diff --git a/README.md b/README.md index 7af8531..bca463f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,22 @@ Dependências: * reportlab * Jinja2 +Roadmap +-------------- +Teste unitários + +Emissão de NFCe + +Compatibilidade [python 2 e 3](https://github.com/danimaribeiro/PyTrustNFe/pull/6) + +Implementar novos provedores de NFSe +* [Betha](cidades/betha.md) - 81 cidades atendidas WIP +* [GINFES](cidades/ginfes.md) - 79 cidades atendidas +* [WebISS](cidades/webiss.md) - 51 cidades atendidas +* [ISSIntel](cidades/issintel.md) - 32 cidades atendidas +* [ISSNET](cidades/issnet.md) - 32 cidades atendidas +* [Saatri](cidades/saatri.md) - 31 cidades atendidas + Exemplos de uso da NFe --------------- @@ -21,13 +37,13 @@ Exemplos de uso da NFe Consulta Cadastro por CNPJ: ```python - from pytrustnfe.nfe import consulta_cadastro - from pytrustnfe.certificado import Certificado +from pytrustnfe.nfe import consulta_cadastro +from pytrustnfe.certificado import Certificado - certificado = open("/path/certificado.pfx", "r").read() - certificado = Certificado(certificado, 'senha_pfx') - obj = {'cnpj': '12345678901234', 'estado': '42'} - resposta = consulta_cadastro(certificado, obj=obj, ambiente=1, estado='42') +certificado = open("/path/certificado.pfx", "r").read() +certificado = Certificado(certificado, 'senha_pfx') +obj = {'cnpj': '12345678901234', 'estado': '42'} +resposta = consulta_cadastro(certificado, obj=obj, ambiente=1, estado='42') ``` @@ -37,85 +53,87 @@ Exemplo de uso da NFSe Paulistana Envio de RPS por lote ```python - certificado = open('/path/certificado.pfx', 'r').read() - certificado = Certificado(certificado, '123456') - # Necessário criar um dicionário com os dados, validação dos dados deve - # ser feita pela aplicação que está utilizando a lib - rps = [ - { - 'assinatura': '123', - 'serie': '1', - 'numero': '1', - 'data_emissao': '2016-08-29', - 'codigo_atividade': '07498', - 'total_servicos': '2.00', - 'total_deducoes': '3.00', - 'prestador': { - 'inscricao_municipal': '123456' - }, - 'tomador': { - 'tipo_cpfcnpj': '1', - 'cpf_cnpj': '12345678923256', - 'inscricao_municipal': '123456', - 'razao_social': 'Trustcode', - 'tipo_logradouro': '1', - 'logradouro': 'Vinicius de Moraes, 42', - 'numero': '42', - 'bairro': 'Corrego', - 'cidade': 'Floripa', - 'uf': 'SC', - 'cep': '88037240', - }, - 'codigo_atividade': '07498', - 'aliquota_atividade': '5.00', - 'descricao': 'Venda de servico' - } - ] - nfse = { - 'cpf_cnpj': '12345678901234', - 'data_inicio': '2016-08-29', - 'data_fim': '2016-08-29', - 'lista_rps': rps +certificado = open('/path/certificado.pfx', 'r').read() +certificado = Certificado(certificado, '123456') +# Necessário criar um dicionário com os dados, validação dos dados deve +# ser feita pela aplicação que está utilizando a lib +rps = [ + { + 'assinatura': '123', + 'serie': '1', + 'numero': '1', + 'data_emissao': '2016-08-29', + 'codigo_atividade': '07498', + 'valor_servico': '2.00', + 'valor_deducao': '3.00', + 'prestador': { + 'inscricao_municipal': '123456' + }, + 'tomador': { + 'tipo_cpfcnpj': '1', + 'cpf_cnpj': '12345678923256', + 'inscricao_municipal': '123456', + 'razao_social': 'Trustcode', + 'tipo_logradouro': '1', + 'logradouro': 'Vinicius de Moraes, 42', + 'numero': '42', + 'bairro': 'Corrego', + 'cidade': '4205407', # Código da cidade, de acordo com o IBGE + 'uf': 'SC', + 'cep': '88037240', + }, + 'codigo_atividade': '07498', + 'aliquota_atividade': '5.00', + 'descricao': 'Venda de servico' } - - retorno = envio_lote_rps(certificado, nfse=nfse) - # retorno é um dicionário { 'received_xml':'', 'sent_xml':'', 'object': object() } - print retorno['received_xml'] - print retorno['sent_xml'] - - # retorno['object'] é um objeto python criado apartir do xml de resposta - print retorno['object'].Cabecalho.Sucesso - print retorno['object'].ChaveNFeRPS.ChaveNFe.NumeroNFe - print retorno['object'].ChaveNFeRPS.ChaveRPS.NumeroRPS +] +nfse = { + 'cpf_cnpj': '12345678901234', + 'data_inicio': '2016-08-29', + 'data_fim': '2016-08-29', + 'total_servicos': '2.00', + 'total_deducoes': '3.00', + 'lista_rps': rps +} + +retorno = envio_lote_rps(certificado, nfse=nfse) +# retorno é um dicionário { 'received_xml':'', 'sent_xml':'', 'object': object() } +print retorno['received_xml'] +print retorno['sent_xml'] + +# retorno['object'] é um objeto python criado apartir do xml de resposta +print retorno['object'].Cabecalho.Sucesso +print retorno['object'].ChaveNFeRPS.ChaveNFe.NumeroNFe +print retorno['object'].ChaveNFeRPS.ChaveRPS.NumeroRPS ``` Cancelamento de NFSe: ```python - from pytrustnfe.certificado import Certificado - from pytrustnfe.nfse.paulistana import cancelamento_nfe - - certificado = open('/path/certificado.pfx', 'r').read() - certificado = Certificado(certificado, '123456') - cancelamento = { - 'cnpj_remetente': '123', - 'assinatura': 'assinatura', - 'numero_nfse': '456', - 'inscricao_municipal': '654', - 'codigo_verificacao': '789', - } - - retorno = cancelamento_nfe(certificado, cancelamento=cancelamento) - - # retorno é um dicionário { 'received_xml':'', 'sent_xml':'', 'object': object() } - print retorno['received_xml'] - print retorno['sent_xml'] - - # retorno['object'] é um objeto python criado apartir do xml de resposta - print retorno['object'].Cabecalho.Sucesso - - if not retorno['object'].Cabecalho.Sucesso: # Cancelamento com erro - print retorno['object'].Erro.Codigo - print retorno['object'].Erro.Descricao +from pytrustnfe.certificado import Certificado +from pytrustnfe.nfse.paulistana import cancelamento_nfe + +certificado = open('/path/certificado.pfx', 'r').read() +certificado = Certificado(certificado, '123456') +cancelamento = { + 'cnpj_remetente': '123', + 'assinatura': 'assinatura', + 'numero_nfse': '456', + 'inscricao_municipal': '654', + 'codigo_verificacao': '789', +} + +retorno = cancelamento_nfe(certificado, cancelamento=cancelamento) + +# retorno é um dicionário { 'received_xml':'', 'sent_xml':'', 'object': object() } +print retorno['received_xml'] +print retorno['sent_xml'] + +# retorno['object'] é um objeto python criado apartir do xml de resposta +print retorno['object'].Cabecalho.Sucesso + +if not retorno['object'].Cabecalho.Sucesso: # Cancelamento com erro + print retorno['object'].Erro.Codigo + print retorno['object'].Erro.Descricao ``` diff --git a/cidades/betha.md b/cidades/betha.md new file mode 100644 index 0000000..8c5607b --- /dev/null +++ b/cidades/betha.md @@ -0,0 +1,53 @@ +* Água Boa - MT +* Alfenas - MG +* Almirante Tamandaré - PR +* Barracão - PR +* Braço do Norte - SC +* Bento Gonçalves - RS +* Bombinhas - SC +* Capão da Canoa - RS +* Capinzal - SC +* Catanduvas - SC +* Chapecó - SC +* Cocal do Sul - SC +* Congonhas - MG +* Cornélio Procópio - PR +* Criciúma - SC +* Dionísio Cerqueira - SC +* Imbituba - SC +* Garopaba - SC +* General Carneiro - PR +* Goioerê - PR +* Fazenda Rio Grande - PR +* Juti - MS +* Joaçaba - SC +* Itapiranga - SC +* Itaú de Minas - MG +* Lages - SC +* Laguna - SC +* Mandaguaçu - PR +* Mandirituba - PR +* Maravilha - SC +* Mariana - MG +* Mococa - SP +* Morro da Fumaça - SC +* Navegantes - SC +* Nova Andradina - MS +* Orlândia - SP +* Orleans - SC +* Paranavaí - PR +* Pinhalzinho - SC +* Santa Rosa de Viterbo - SP +* Santo Amaro da Imperatriz - SC. +* São Joaquim - SC +* São José - SC +* São Mateus do Sul - PR +* São Miguel do Oeste - SC +* Sombrio - SC +* Tijucas - SC +* Torres - RS +* União da Vitória - PR +* Urussanga - SC +* Várzea Grande - MT +* Xanxerê - SC +* Xaxim - SC diff --git a/cidades/ginfes.md b/cidades/ginfes.md new file mode 100644 index 0000000..cf8e0ce --- /dev/null +++ b/cidades/ginfes.md @@ -0,0 +1,62 @@ +* Amparo - SP +* Arapiraca - AL +* Araraquara - SP +* Araxá - MG +* Belford Roxo - RJ +* Betim - MG +* Caraguatatuba - SP +* Caruaru - PE +* Capivari - SP +* Cataguases - MG +* Cotia - SP +* Diadema - SP +* Eusébio - CE +* Fortaleza - CE +* Franca - SP +* Guaíba - RS +* Guaratinguetá - SP +* Guarujá - SP +* Guarulhos - SP +* Hortolândia - SP +* Itaboraí - RJ +* Itabira - MG +* Itajuba - MG +* Itaúna - MG +* Itu - SP +* Jaboticabal - SP +* Jardinópolis - SP +* Jaú - SP +* Jundiaí - SP +* Lagoa Santa - MG +* Maceió - AL +* Manaus - AM +* Morro Agudo - SP +* Mauá - SP +* Muriaé - MG +* Olímpia - SP +* Paulínia - SP +* Pelotas - RS +* Poços de Caldas - MG +* Porto Ferreira - SP +* Pouso Alegre - MG +* Ribeirão das Neves - MG +* Ribeirão Pires - SP +* Ribeirão Preto - SP +* Rio Claro - SP +* Salto - SP +* Santa Rita do Passa Quatro - SP +* Santo André - SP +* Santos - SP +* São Bernardo do Campo - SP +* São Caetano do Sul - SP +* São Carlos - SP +* São José do Rio Preto - SP +* São José dos Campos - SP +* São Roque - SP +* Sarzedo - MG +* Suzano - SP +* Taquaritinga - SP +* Ubá - MG +* Ubatuba - SP +* Umuarama - PR +* Votuporanga - SP diff --git a/cidades/issintel.md b/cidades/issintel.md new file mode 100644 index 0000000..e69de29 diff --git a/cidades/issnet.md b/cidades/issnet.md new file mode 100644 index 0000000..e69de29 diff --git a/cidades/saatri.md b/cidades/saatri.md new file mode 100644 index 0000000..dbea57f --- /dev/null +++ b/cidades/saatri.md @@ -0,0 +1,8 @@ +* Barreiras - BA +* Boa Vista - RR +* Bom Jesus da Lapa - BA +* Catu - BA +* Eunápolis - BA +* Ipiaú - BA +* Jacobina - BA +* São Sebastião de Passé - BA diff --git a/cidades/webiss.md b/cidades/webiss.md new file mode 100644 index 0000000..4c25ac1 --- /dev/null +++ b/cidades/webiss.md @@ -0,0 +1,32 @@ +* Aracajú - SE +* Arcos - MG +* Bagé - RS +* Barbacena - MG +* Brumado - BA. +* Campo Belo - MG +* Candeias - BA +* Cássia - MG +* Caldas Novas - GO +* Coronel Fabriciano - MG +* Estância - SE +* Extrema - MG +* Feira de Santana–BA +* Formiga - MG +* Guanambi - BA +* Itabuna - BA +* Itapetinga - BA +* Lagarto - SE +* Lucas do Rio Verde - MT +* Luís Eduardo Magalhães - BA +* Niterói - RJ +* Nova Serrana - MG +* Palmas - TO +* Passos - MG +* Porto Nacional - TO +* Santa Rita do Sapucai - MG +* São Gotardo - MG +* São Lourenço - MG +* Tangará da Serra - MT +* Teresópolis - RJ +* Uberaba-MG +* Vitória da Conquista - BA diff --git a/pytrustnfe/Servidores.py b/pytrustnfe/Servidores.py index 249b9aa..d679e70 100644 --- a/pytrustnfe/Servidores.py +++ b/pytrustnfe/Servidores.py @@ -10,15 +10,15 @@ WS_NFE_CONSULTA = 'NfeConsultaProtocolo' WS_NFE_SITUACAO = 'NfeStatusServico' WS_NFE_CADASTRO = 'NfeConsultaCadastro' -WS_NFCE_AUTORIZACAO = 'NfceAutorizacao' -WS_NFCE_RET_AUTORIZACAO = 'NfceRetAutorizacao' +WS_NFCE_AUTORIZACAO = 'NfeAutorizacao' +WS_NFCE_RET_AUTORIZACAO = 'NfeRetAutorizacao' WS_NFCE_CANCELAMENTO = 'RecepcaoEventoCancelamento' -WS_NFCE_INUTILIZACAO = 'NfceInutilizacao' -WS_NFCE_CONSULTA = 'NfceConsultaProtocolo' -WS_NFCE_SITUACAO = 'NfceStatusServico' -WS_NFCE_CADASTRO = 'NfceConsultaCadastro' +WS_NFCE_INUTILIZACAO = 'NfeInutilizacao' +WS_NFCE_CONSULTA = 'NfeConsultaProtocolo' +WS_NFCE_SITUACAO = 'NfeStatusServico' +WS_NFCE_CADASTRO = 'NfeConsultaCadastro' WS_NFCE_RECEPCAO_EVENTO = 'RecepcaoEventoCarta' -WS_NFCE_QR_CODE = 'NcfeQRCode' +WS_NFCE_QR_CODE = 'NfeQRCode' WS_NFE_CADASTRO = 'NfeConsultaCadastro' WS_DPEC_RECEPCAO = 'RecepcaoEventoEPEC' @@ -34,8 +34,8 @@ NFE_AMBIENTE_HOMOLOGACAO = 2 NFCE_AMBIENTE_PRODUCAO = 1 NFCE_AMBIENTE_HOMOLOGACAO = 2 -NFE_MODELO = 55 -NFCE_MODELO = 65 +NFE_MODELO = u'55' +NFCE_MODELO = u'65' SIGLA_ESTADO = { '12': 'AC', @@ -69,11 +69,9 @@ SIGLA_ESTADO = { def localizar_url(servico, estado, mod=55, ambiente=2): - import pdb - pdb.set_trace() sigla = SIGLA_ESTADO[estado] - dominio = ESTADO_WS[sigla][ambiente]['servidor'] - complemento = ESTADO_WS[sigla][ambiente][servico] + dominio = ESTADO_WS[sigla][mod][ambiente]['servidor'] + complemento = ESTADO_WS[sigla][mod][ambiente][servico] if sigla == 'RS' and servico == WS_NFE_CADASTRO: dominio = 'cad.sefazrs.rs.gov.br' @@ -84,6 +82,14 @@ def localizar_url(servico, estado, mod=55, ambiente=2): return "https://%s/%s" % (dominio, complemento) +def localizar_qrcode(estado, ambiente=2): + sigla = SIGLA_ESTADO[estado] + dominio = ESTADO_WS[sigla]['65'][ambiente]['servidor'] + complemento = ESTADO_WS[sigla]['65'][ambiente][WS_NFCE_QR_CODE] + + return "https://%s/%s" % (dominio, complemento) + + METODO_WS = { WS_NFE_AUTORIZACAO: { 'webservice': 'NfeAutorizacao', @@ -131,6 +137,7 @@ SVRS = { NFE_AMBIENTE_PRODUCAO: { 'servidor': 'nfe.svrs.rs.gov.br', WS_NFE_RECEPCAO_EVENTO: 'ws/recepcaoevento/recepcaoevento.asmx', + WS_NFE_CANCELAMENTO: 'ws/recepcaoevento/recepcaoevento.asmx', WS_NFE_AUTORIZACAO: 'ws/NfeAutorizacao/NfeAutorizacao.asmx', WS_NFE_RET_AUTORIZACAO: 'ws/NfeRetAutorizacao/NfeRetAutorizacao.asmx', WS_NFE_CADASTRO: 'ws/CadConsultaCadastro/CadConsultaCadastro2.asmx', @@ -141,6 +148,7 @@ SVRS = { NFE_AMBIENTE_HOMOLOGACAO: { 'servidor': 'nfe-homologacao.svrs.rs.gov.br', WS_NFE_RECEPCAO_EVENTO: 'ws/recepcaoevento/recepcaoevento.asmx', + WS_NFE_CANCELAMENTO: 'ws/recepcaoevento/recepcaoevento.asmx', WS_NFE_AUTORIZACAO: 'ws/NfeAutorizacao/NfeAutorizacao.asmx', WS_NFE_RET_AUTORIZACAO: 'ws/NfeRetAutorizacao/NfeRetAutorizacao.asmx', WS_NFE_CADASTRO: 'ws/CadConsultaCadastro/CadConsultaCadastro2.asmx', @@ -486,6 +494,7 @@ UFRS = { WS_NFE_INUTILIZACAO: 'ws/NfeInutilizacao/NfeInutilizacao2.asmx', WS_NFE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx', WS_NFE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx', + WS_NFE_CANCELAMENTO: 'ws/recepcaoevento/recepcaoevento.asmx', }, NFE_AMBIENTE_HOMOLOGACAO: { 'servidor': 'nfe-homologacao.sefazrs.rs.gov.br', @@ -498,6 +507,7 @@ UFRS = { WS_NFE_INUTILIZACAO: 'ws/NfeInutilizacao/NfeInutilizacao2.asmx', WS_NFE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx', WS_NFE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx', + WS_NFE_CANCELAMENTO: 'ws/recepcaoevento/recepcaoevento.asmx', } } @@ -546,7 +556,7 @@ UFSP = { WS_NFCE_SITUACAO: 'ws/nfestatusservico2.asmx', WS_NFCE_CADASTRO: 'ws/cadconsultacadastro2.asmx', WS_NFCE_RECEPCAO_EVENTO: 'ws/recepcaoevento.asmx', - WS_NFCE_QR_CODE: '/NFCEConsultaPublica/Paginas/ConstultaQRCode.aspx', + WS_NFCE_QR_CODE: 'NFCEConsultaPublica/Paginas/ConstultaQRCode.aspx', } } } diff --git a/pytrustnfe/nfe/__init__.py b/pytrustnfe/nfe/__init__.py index c3846ac..c8926c4 100644 --- a/pytrustnfe/nfe/__init__.py +++ b/pytrustnfe/nfe/__init__.py @@ -10,7 +10,7 @@ from .assinatura import Assinatura from pytrustnfe.xml import render_xml from pytrustnfe.utils import CabecalhoSoap from pytrustnfe.utils import gerar_chave, ChaveNFe -from pytrustnfe.Servidores import localizar_url +from pytrustnfe.Servidores import localizar_url, localizar_qrcode def _build_header(method, **kwargs): @@ -18,6 +18,7 @@ def _build_header(method, **kwargs): 'NfeAutorizacao': ('NfeAutorizacao', '3.10'), 'NfeRetAutorizacao': ('NfeRetAutorizacao', '3.10'), 'NfeConsultaCadastro': ('CadConsultaCadastro2', '2.00'), + 'RecepcaoEventoCancelamento': ('RecepcaoEvento', '1.00') } vals = {'estado': kwargs['estado'], 'soap_action': action[method][0], @@ -55,14 +56,36 @@ def _add_required_node(elemTree): cEan = etree.Element('cEAN') cEANTrib = etree.Element('cEANTrib') prod.insert(1, cEan) - vProd = prod.find('ns:vProd', namespaces = ns) + vProd = prod.find('ns:vProd', namespaces=ns) prod.insert(prod.index(vProd) + 1, cEANTrib) return elemTree +def _add_qrCode(xml, **kwargs): + xml = etree.fromstring(xml) + inf_nfe = kwargs['NFes'][0]['infNFe'] + nfe = xml.find(".//{http://www.portalfiscal.inf.br/nfe}NFe") + infnfesupl = etree.Element('infNFeSupl') + qrcode = etree.Element('qrCode') + qrcode_url = localizar_qrcode(kwargs['estado'], kwargs['ambiente']) + chave_nfe = inf_nfe['Id'][3:] + dh_emissao = inf_nfe['ide']['dhEmi'].encode('hex') + versao = 100 + ambiente = kwargs['ambiente'] + valor_total = inf_nfe['total']['vNF'] + if inf_nfe.get('dest', False): + dest_cpf = inf_nfe['dest'].get('CPF', False) + icms_total = inf_nfe['total']['vICMS'] + dig_val_tag = xml.find( + ".//{http://www.portalfiscal.inf.br/nfe}Signature/SignedInfo/Reference/DigestValue") + dig_val = dig_val_tag.text.encode('hex') + + qrcode_text = qrcode_url + + def _send(certificado, method, sign, **kwargs): path = os.path.join(os.path.dirname(__file__), 'templates') - + modelo = kwargs['NFes'][0]['infNFe']['ide']['mod'] xmlElem_send = render_xml(path, '%s.xml' % method, True, **kwargs) if sign: # Caso for autorização temos que adicionar algumas tags tipo @@ -71,12 +94,21 @@ def _send(certificado, method, sign, **kwargs): xmlElem_send = _add_required_node(xmlElem_send) signer = Assinatura(certificado.pfx, certificado.password) - xml_send = signer.assina_xml( - xmlElem_send, kwargs['NFes'][0]['infNFe']['Id']) + if method == 'NfeAutorizacao': + xml_send = signer.assina_xml( + xmlElem_send, kwargs['NFes'][0]['infNFe']['Id']) + elif method == 'RecepcaoEventoCancelamento': + xml_send = signer.assina_xml( + xmlElem_send, kwargs['eventos'][0]['Id']) + + if modelo == '65': + _add_qrCode(xml_send, **kwargs) + else: xml_send = etree.tostring(xmlElem_send) - url = localizar_url(method, kwargs['estado'], kwargs['ambiente']) + url = localizar_url(method, kwargs['estado'], modelo, + kwargs['ambiente']) cabecalho = _build_header(method, **kwargs) response, obj = executar_consulta(certificado, url, cabecalho, xml_send) diff --git a/pytrustnfe/nfe/assinatura.py b/pytrustnfe/nfe/assinatura.py index 9520ace..7104e9b 100644 --- a/pytrustnfe/nfe/assinatura.py +++ b/pytrustnfe/nfe/assinatura.py @@ -33,6 +33,10 @@ class Assinatura(object): signed_root = signer.sign( xml_element, key=key, cert=cert, reference_uri=('#%s' % reference)) - if len(signed_root) > 3: - signed_root[2].append(signed_root[3]) + element_signed = signed_root.find(".//*[@Id='%s']" % reference) + signature = signed_root.find( + ".//{http://www.w3.org/2000/09/xmldsig#}Signature") + if element_signed and signature: + parent = element_signed.getparent() + parent.append(signature) return etree.tostring(signed_root) diff --git a/pytrustnfe/nfe/templates/NfeAutorizacao.xml b/pytrustnfe/nfe/templates/NfeAutorizacao.xml index eeb2d9b..b287860 100644 --- a/pytrustnfe/nfe/templates/NfeAutorizacao.xml +++ b/pytrustnfe/nfe/templates/NfeAutorizacao.xml @@ -18,7 +18,7 @@ {{ ide.dhSaiEnt }} {% endif %} {{ ide.tpNF }} - {{ ide.idDest }} + {{ ide.idDest }} {{ ide.cMunFG }} {{ ide.tpImp }} {{ ide.tpEmis }} @@ -99,7 +99,7 @@ {{ emit.CRT }} {% endwith %} - {% if dest is defined %} + {% if NFe.infNFe.dest is defined %} {% with dest = NFe.infNFe.dest %} {% if dest.tipo == 'person' -%} @@ -127,8 +127,8 @@ {{ dest.IM }} {{ dest.email }} {% endwith %} - - {% endif %} + + {% endif %} {% if NFe.infNFe.retirada is defined %} {{ NFe.infNFe.retirada.CNPJ }} diff --git a/pytrustnfe/nfe/templates/RecepcaoEventoCancelamento.xml b/pytrustnfe/nfe/templates/RecepcaoEventoCancelamento.xml index 91ed2a9..9b4b993 100644 --- a/pytrustnfe/nfe/templates/RecepcaoEventoCancelamento.xml +++ b/pytrustnfe/nfe/templates/RecepcaoEventoCancelamento.xml @@ -1,20 +1,22 @@ - {{ obj.lote }} + {{ idLote }} + {% for evento in eventos %} - - {{ obj.orgao }} - {{ obj.ambiente }} - {{ obj.cnpj }} - {{ obj.chave_nfe }} - {{ obj.data_hora_evento }} + + {{ evento.cOrgao }} + {{ evento.tpAmb }} + {{ evento.CNPJ }} + {{ evento.chNFe }} + {{ evento.dhEvento }} 110111 - {{ obj.numero_evento }} + {{ evento.nSeqEvento }} 1.00 Cancelamento - {{ obj.protocolo }} - {{obj.justificativa }} + {{ evento.nProt }} + {{ evento.xJust }} - \ No newline at end of file + {% endfor %} + diff --git a/pytrustnfe/nfse/betha/__init__.py b/pytrustnfe/nfse/betha/__init__.py new file mode 100644 index 0000000..f2517fa --- /dev/null +++ b/pytrustnfe/nfse/betha/__init__.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import os +import suds +from OpenSSL import crypto +from base64 import b64encode +from pytrustnfe.xml import render_xml, sanitize_response +from pytrustnfe.client import get_authenticated_client +from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key +from pytrustnfe.nfse.assinatura import Assinatura + + +def sign_tag(certificado, **kwargs): + pkcs12 = crypto.load_pkcs12(certificado.pfx, certificado.password) + key = pkcs12.get_privatekey() + if 'nfse' in kwargs: + for item in kwargs['nfse']['lista_rps']: + signed = crypto.sign(key, item['assinatura'], 'SHA1') + item['assinatura'] = b64encode(signed) + if 'cancelamento' in kwargs: + signed = crypto.sign(key, kwargs['cancelamento']['assinatura'], 'SHA1') + kwargs['cancelamento']['assinatura'] = b64encode(signed) + + +def _send(certificado, method, **kwargs): + path = os.path.join(os.path.dirname(__file__), 'templates') + if method in ('GerarNfse', 'RecepcionarLoteRps', + 'RecepcionarLoteRpsSincrono', + 'CancelarNfse', 'SubstituirNfse'): + sign_tag(certificado, **kwargs) + + if kwargs['ambiente'] == 'producao': + url = \ + 'http://e-gov.betha.com.br/e-nota-contribuinte-test-ws/nfseWS?wsdl' + else: + url = 'http://e-gov.betha.com.br/e-nota-contribuinte-ws/nfseWS?wsdl' + + xml_send = render_xml(path, '%s.xml' % method, False, **kwargs) + + cert, key = extract_cert_and_key_from_pfx( + certificado.pfx, certificado.password) + cert, key = save_cert_key(cert, key) + client = get_authenticated_client(url, cert, key) + + pfx_path = certificado.save_pfx() + signer = Assinatura(pfx_path, certificado.password) + xml_send = signer.assina_xml(xml_send, '') + + try: + response = getattr(client.service, method)(1, xml_send) + except suds.WebFault, e: + return { + 'sent_xml': xml_send, + 'received_xml': e.fault.faultstring, + 'object': None + } + + response, obj = sanitize_response(response) + return { + 'sent_xml': xml_send, + 'received_xml': response, + 'object': obj + } + + +def gerar_nfse(certificado, **kwargs): + return _send(certificado, 'GerarNfse', **kwargs) + + +def envio_lote_rps_assincrono(certificado, **kwargs): + return _send(certificado, 'RecepcionarLoteRps', **kwargs) + + +def envio_lote_rps(certificado, **kwargs): + return _send(certificado, 'RecepcionarLoteRpsSincrono', **kwargs) + + +def cancelar_nfse(certificado, **kwargs): + return _send(certificado, 'CancelarNfse', **kwargs) + + +def substituir_nfse(certificado, **kwargs): + return _send(certificado, 'SubstituirNfse', **kwargs) + + +def consulta_situacao_lote_rps(certificado, **kwargs): + return _send(certificado, 'ConsultaSituacaoLoteRPS', **kwargs) + + +def consulta_nfse_por_rps(certificado, **kwargs): + return _send(certificado, 'ConsultaNfsePorRps', **kwargs) + + +def consultar_lote_rps(certificado, **kwargs): + return _send(certificado, 'ConsultarLoteRps', **kwargs) + + +def consulta_nfse_servico_prestado(certificado, **kwargs): + return _send(certificado, 'ConsultarNfseServicoPrestado', **kwargs) + + +def consultar_nfse_servico_tomado(certificado, **kwargs): + return _send(certificado, 'ConsultarNfseServicoTomado', **kwargs) + + +def consulta_nfse_faixe(certificado, **kwargs): + return _send(certificado, 'ConsultarNfseFaixa', **kwargs) + + +def consulta_cnpj(certificado, **kwargs): + return _send(certificado, 'ConsultaCNPJ', **kwargs) diff --git a/pytrustnfe/nfse/betha/templates/CancelarNfse.xml b/pytrustnfe/nfse/betha/templates/CancelarNfse.xml new file mode 100644 index 0000000..ecb5a16 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/CancelarNfse.xml @@ -0,0 +1,15 @@ + + + + + 58 + + 45111111111100 + + 123498 + 4204608 + + 1 + + + diff --git a/pytrustnfe/nfse/betha/templates/ConsultarLoteRps.xml b/pytrustnfe/nfse/betha/templates/ConsultarLoteRps.xml new file mode 100644 index 0000000..3861c49 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/ConsultarLoteRps.xml @@ -0,0 +1,8 @@ + + + + 45111111111100 + + + 141542179222170 + diff --git a/pytrustnfe/nfse/betha/templates/ConsultarNfseFaixa.xml b/pytrustnfe/nfse/betha/templates/ConsultarNfseFaixa.xml new file mode 100644 index 0000000..b223234 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/ConsultarNfseFaixa.xml @@ -0,0 +1,13 @@ + + + + 45111111111100 + + 123498 + + + 50 + 60 + + 1 + diff --git a/pytrustnfe/nfse/betha/templates/ConsultarNfsePorRps.xml b/pytrustnfe/nfse/betha/templates/ConsultarNfsePorRps.xml new file mode 100644 index 0000000..e86e16b --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/ConsultarNfsePorRps.xml @@ -0,0 +1,13 @@ + + + 24 + A1 + 1 + + + + 45111111111100 + + 123498 + + diff --git a/pytrustnfe/nfse/betha/templates/ConsultarNfseServicoPrestado.xml b/pytrustnfe/nfse/betha/templates/ConsultarNfseServicoPrestado.xml new file mode 100644 index 0000000..421df7b --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/ConsultarNfseServicoPrestado.xml @@ -0,0 +1,13 @@ + + + + 45111111111100 + + + 61 + + 2014-12-01 + 2014-12-31 + + 1 + diff --git a/pytrustnfe/nfse/betha/templates/ConsultarNfseServicoTomado.xml b/pytrustnfe/nfse/betha/templates/ConsultarNfseServicoTomado.xml new file mode 100644 index 0000000..1893693 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/ConsultarNfseServicoTomado.xml @@ -0,0 +1,27 @@ + + + + 45111111111100 + + + + 2014-01-01 + 2014-12-31 + + + + 45111111111100 + + + + + 83787494000123 + + + + + 45111111111100 + + + 1 + diff --git a/pytrustnfe/nfse/betha/templates/GerarNfse.xml b/pytrustnfe/nfse/betha/templates/GerarNfse.xml new file mode 100644 index 0000000..fdd22d1 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/GerarNfse.xml @@ -0,0 +1,3 @@ + + {% include 'rps.xml' %} + diff --git a/pytrustnfe/nfse/betha/templates/RecepcionarLoteRps.xml b/pytrustnfe/nfse/betha/templates/RecepcionarLoteRps.xml new file mode 100644 index 0000000..0b11051 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/RecepcionarLoteRps.xml @@ -0,0 +1,13 @@ + + + 2012024 + + 45111111111100 + + 123498 + 1 + + {% include 'rps.xml' %} + + + diff --git a/pytrustnfe/nfse/betha/templates/RecepcionarLoteRpsSincrono.xml b/pytrustnfe/nfse/betha/templates/RecepcionarLoteRpsSincrono.xml new file mode 100644 index 0000000..79f6eda --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/RecepcionarLoteRpsSincrono.xml @@ -0,0 +1,13 @@ + + + 2012021 + + 45111111111100 + + 123498 + 1 + + {% include 'rps.xml' %} + + + diff --git a/pytrustnfe/nfse/betha/templates/Rps.xml b/pytrustnfe/nfse/betha/templates/Rps.xml new file mode 100644 index 0000000..2650c87 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/Rps.xml @@ -0,0 +1,78 @@ + + + + + 25 + A1 + 1 + + 2014-12-06 + 1 + + 2014-12-01 + + + 100 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + 2 + 0702 + 2525 + Prog. + 4204608 + 1 + 4204608 + + + + 45111111111100 + + 123498 + + + + + 83787494000123 + + + INSTITUICAO FINANCEIRA + + AV. 7 DE SETEMBRO + 1505 + AO LADO DO JOAO AUTOMOVEIS + CENTRO + 4201406 + SC + 88900000 + + + 4835220026 + luiz.alves@cxpostal.com + + + + + + 06410987065144 + + 22252 + + CONSTRUTORA TERRA FIRME + + + 142 + 1/2014 + + 3 + 2 + 2 + + diff --git a/pytrustnfe/nfse/betha/templates/SubstituirNfse.xml b/pytrustnfe/nfse/betha/templates/SubstituirNfse.xml new file mode 100644 index 0000000..294c0d0 --- /dev/null +++ b/pytrustnfe/nfse/betha/templates/SubstituirNfse.xml @@ -0,0 +1,18 @@ + + + + + + 57 + + 45111111111100 + + 123498 + 4204608 + + 2 + + + {% include 'rps.xml' %} + + diff --git a/pytrustnfe/nfse/ginfes/__init__.py b/pytrustnfe/nfse/ginfes/__init__.py new file mode 100644 index 0000000..449d2f8 --- /dev/null +++ b/pytrustnfe/nfse/ginfes/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/pytrustnfe/nfse/issintel/__init__.py b/pytrustnfe/nfse/issintel/__init__.py new file mode 100644 index 0000000..449d2f8 --- /dev/null +++ b/pytrustnfe/nfse/issintel/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/pytrustnfe/nfse/issnet/__init__.py b/pytrustnfe/nfse/issnet/__init__.py new file mode 100644 index 0000000..449d2f8 --- /dev/null +++ b/pytrustnfe/nfse/issnet/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/pytrustnfe/nfse/paulistana/templates/EnvioLoteRPS.xml b/pytrustnfe/nfse/paulistana/templates/EnvioLoteRPS.xml index 33a3fcd..796daa1 100644 --- a/pytrustnfe/nfse/paulistana/templates/EnvioLoteRPS.xml +++ b/pytrustnfe/nfse/paulistana/templates/EnvioLoteRPS.xml @@ -18,20 +18,20 @@ {{ rps.serie }} {{ rps.numero }} - RPS + {{ rps.tipo_rps | default('RPS') }} {{ rps.data_emissao }} N - T + {{ rps.tributacao_rps | default('T') }} {{ rps.valor_servico }} {{ rps.valor_deducao }} - 0.00 - 0.00 - 0.00 - 0.00 - 0.00 + {{ rps.valor_pis | default('0.00') }} + {{ rps.valor_cofins | default('0.00') }} + {{ rps.valor_inss | default('0.00') }} + {{ rps.valor_ir | default('0.00') }} + {{ rps.valor_csll | default('0.00') }} {{ rps.codigo_atividade }} {{ rps.aliquota_atividade }} - false + {{ rps.iss_retido | default('false') }} {% if rps.tomador.tipo_cpfcnpj == 1 -%} {{ rps.tomador.cpf_cnpj }} diff --git a/pytrustnfe/nfse/saatri/__init__.py b/pytrustnfe/nfse/saatri/__init__.py new file mode 100644 index 0000000..449d2f8 --- /dev/null +++ b/pytrustnfe/nfse/saatri/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/pytrustnfe/nfse/webiss/__init__.py b/pytrustnfe/nfse/webiss/__init__.py new file mode 100644 index 0000000..449d2f8 --- /dev/null +++ b/pytrustnfe/nfse/webiss/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/pytrustnfe/test/XMLs/paulistana_signature.xml b/pytrustnfe/test/XMLs/paulistana_signature.xml new file mode 100644 index 0000000..013053e --- /dev/null +++ b/pytrustnfe/test/XMLs/paulistana_signature.xml @@ -0,0 +1,37 @@ + +12345678901234false2016-08-292016-08-291E4fpHYkQa7Naxn6IKGb7NwwZu5tPk/KXJ9hCwtZgq0xvKS450aQqqBL+7Iv46lTgqrSMu7+gLrl+LC1qs/8aT2mbHE8uaVFSbzwZ+sF/BkcT6nsFHLMswEiTAEs95Jb7hN1cC91xqQGRH4buw0TzxHKmhuLJ22WwtG/scxyKtjM=12345611RPS2016-08-29NT0.000.000.000.000.00074985.00false + + + 123456Trustcode1Vinicius de Moraes, 4242CorregoFloripaSC88037240Venda de servico + + + + + + + + + +ivaOwkcrt0pfuMYsAdfyLaUAcIk= + + +FjIHdfPavSEyaWYhAT0z0shPLuTsqBKyy78PUEZ8PUhTZ+iSV0MOvAIRq9MPPVK9 +jjXOw1TE903uSK8aJon52RNKPd68ORVJ3bKFSjTqQLxFRR9tiiAQFrWDETf7FF89 +EhG6dy6TGcgVbOyn0Jqm8MkqrE1XrJ44orN1X+Jt+7U= + + +MIICMTCCAZqgAwIBAgIQfYOsIEVuAJ1FwwcTrY0t1DANBgkqhkiG9w0BAQUFADBX +MVUwUwYDVQQDHkwAewA1ADkARgAxAEUANAA2ADEALQBEAEQARQA1AC0ANABEADIA +RgAtAEEAMAAxAEEALQA4ADMAMwAyADIAQQA5AEUAQgA4ADMAOAB9MB4XDTE1MDYx +NTA1NDc1N1oXDTE2MDYxNDExNDc1N1owVzFVMFMGA1UEAx5MAHsANQA5AEYAMQBF +ADQANgAxAC0ARABEAEUANQAtADQARAAyAEYALQBBADAAMQBBAC0AOAAzADMAMgAy +AEEAOQBFAEIAOAAzADgAfTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAk41G +nqXXLaiOC/y0/cA4tbS+NZCqI+x4EsztgDFvPPlHstiVYcLRkni4i93gK9zoC6g0 +mh66HMVzAfE8vRNwW5b7m6nWS1SiHBon7/Mqsw4MIq3SC+J/fTbKpqwyfAuH2YZl +AiQuQc85fyllAMLh2WrA7JgOLR/5tF3kLtpbHdECAwEAATANBgkqhkiG9w0BAQUF +AAOBgQArdh+RyT6VxKGsXk1zhHsgwXfToe6GpTF4W8PHI1+T0WIsNForDhvst6nm +QtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86 +d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw== + + + diff --git a/pytrustnfe/test/test_nfse_paulistana.py b/pytrustnfe/test/test_nfse_paulistana.py index 2ebd568..0ed4ded 100644 --- a/pytrustnfe/test/test_nfse_paulistana.py +++ b/pytrustnfe/test/test_nfse_paulistana.py @@ -6,15 +6,15 @@ import unittest from pytrustnfe.certificado import Certificado from pytrustnfe.nfse.paulistana import envio_lote_rps from pytrustnfe.nfse.paulistana import cancelamento_nfe +from pytrustnfe.nfse.assinatura import Assinatura +from pytrustnfe.nfse.paulistana import sign_tag class test_nfse_paulistana(unittest.TestCase): caminho = os.path.dirname(__file__) - def test_envio_nfse(self): - pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() - pfx = Certificado(pfx_source, '123456') + def _get_nfse(self): rps = [ { 'assinatura': '123', @@ -51,7 +51,13 @@ class test_nfse_paulistana(unittest.TestCase): 'data_fim': '2016-08-29', 'lista_rps': rps } + return nfse + def test_envio_nfse(self): + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx = Certificado(pfx_source, '123456') + + nfse = self._get_nfse() path = os.path.join(os.path.dirname(__file__), 'XMLs') xml_return = open(os.path.join( path, 'paulistana_resultado.xml'), 'r').read() @@ -70,6 +76,23 @@ class test_nfse_paulistana(unittest.TestCase): self.assertEqual( retorno['object'].ChaveNFeRPS.ChaveRPS.NumeroRPS, 6) + def test_nfse_signature(self): + pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read() + pfx = Certificado(pfx_source, '123456') + + nfse = self._get_nfse() + path = os.path.join(os.path.dirname(__file__), 'XMLs') + xml_sent = open(os.path.join( + path, 'paulistana_signature.xml'), 'r').read() + + with mock.patch('pytrustnfe.nfse.paulistana.get_authenticated_client') as client: + retorno = mock.MagicMock() + client.return_value = retorno + retorno.service.EnvioLoteRPS.return_value = '' + + retorno = envio_lote_rps(pfx, nfse=nfse) + self.assertEqual(retorno['sent_xml'], xml_sent) + def _get_cancelamento(self): return { 'cnpj_remetente': '123',