Compare commits

...

72 Commits

Author SHA1 Message Date
Danimar Ribeiro 966ba5f795 [FIX] XML NFSe maringa and percent difal 8 years ago
Danimar Ribeiro 44bcedc33b Realiza a conversão do retorno corretamente com decode - BH 8 years ago
Danimar Ribeiro 8f4cf56a29 Adição das tags do veículo de transporte no danfe 8 years ago
Danimar Ribeiro b474ec9913 Pequenos ajustes na impressão do danfce 8 years ago
Danimar Ribeiro 4f25b76ec8 Ajuste na url do qrcode amazonas 8 years ago
carcaroff c0dd5d02dd Implementação da danfe para NFC-e 8 years ago
Fábio Luna 5893a0324b Correção de testes. 8 years ago
Fábio Luna c7bc21de2a Retira lixo código. 8 years ago
Fábio Luna 5c9908da57 Insere servidor de cancelamento de NFC-e. 8 years ago
Fábio Luna cab13549cc Correções para envio de NFC-e 8 years ago
Fábio Luna 4f8b752626 [WIP] NFC-e Corrige servidores. 8 years ago
Fábio Luna 1d9f4b0776 [WIP] Implementando NFC-e SVRS 8 years ago
Danimar Ribeiro e712bf0d6a Correção de envio Ginfes - Mudança para o zeep para webservice SOAP 8 years ago
Danimar Ribeiro 3db71b7b9a Nota de BH utiliza duas assinaturas 8 years ago
Danimar Ribeiro 236515b12d Ajuste ao imprimir nfe cancelada, correção de encoding 8 years ago
Johny Chen Jy 4f423e28f5 Implementa timezone ao imprimir a danfe 8 years ago
Danimar Ribeiro 9a1f406c62 Correção no envio de NFe para o estado do Ceará 8 years ago
Danimar Ribeiro 38874665b9 WIP - Implementação da nota de BH, tentativa de usar o zeep 8 years ago
Danimar Ribeiro eb1db219b8 Nova tag de base de cálculo para nfse-floripa 8 years ago
Danimar Ribeiro 7a3f6275b8 Incrementando versão do pyopenssl 8 years ago
Danimar Ribeiro 1b2e206fcc Versão 0.9.15 8 years ago
Johny Chen Jy 2aea7bae5d Update utils.py 8 years ago
carcaroff 1f5645cf8e [FEAT][10.0]Cancelado no DANFE 8 years ago
pal0schi 92a2505726 Correcao NFS-e Floripa - Descrição dos serviços 8 years ago
Felipe ed322847ed decode no sent_xml nfse imperial 8 years ago
Danimar Ribeiro 6760152ed5 Ajuste no readme com novas implementações, remoção do appveyor build 8 years ago
Danimar Ribeiro 42d2a8d1e3 [DONE] Finalização do layout Nota Carioca - Geração e Cancelamento 8 years ago
Danimar Ribeiro 2bbd78c295 Implementação da nota carioca 8 years ago
Danimar Ribeiro 141fecb6a0 FIX - Corrige eventos do manifesto e geração da nfe processada 8 years ago
pal0schi 9c2344c15e Master3 nfse imperial (#110) 8 years ago
pal0schi 105843d719 Master3 bug unicode (#109) 8 years ago
carcaroff 6117a8b63f 'Incrementar versão ;-;' 8 years ago
carcaroff 6b00132c4b [FIX]Normalize e escape em campos de texto 8 years ago
Danimar Ribeiro e1754c8e16 Incremento da versão do suds requests 8 years ago
Danimar Ribeiro 954dfcfecf Incremento de versão 8 years ago
Danimar Ribeiro baca382a0f
Merge pull request #98 from danimaribeiro/master-fix 8 years ago
Danimar Ribeiro f73aadada9
Merge branch 'master3' into master-fix 8 years ago
Danimar Ribeiro 57997f9164 Merge branch 'master3' into master-fix 8 years ago
Danimar Ribeiro 24be4815e1 Incremento de versão 8 years ago
Danimar Ribeiro 55031fe78d Implementação do cancelamento de NFSe - Floripa 8 years ago
Danimar Ribeiro 1f56dd5fa7 Retorna uma exceção correta quando não for possível obter o token 8 years ago
Danimar Ribeiro 7d6aacb655 Incremento de versões das dependencias 8 years ago
Danimar Ribeiro d0ce9460b1
Merge pull request #86 from danimaribeiro/feature/nfse-floripa 8 years ago
Danimar Ribeiro d53a0bb833
Merge branch 'master3' into feature/nfse-floripa 8 years ago
Danimar Ribeiro 78b0e47dfb Finalizado a parte de emissão de NFSe Floripa 8 years ago
pal0schi 7d4f9079c8 * correção da url de campo grande-ms 8 years ago
Felipe 36611c8f81 alteração nfse campinas para dsf 8 years ago
Felipe d2a0c26b1a mudança nfse campinas para dsf, adicionadas outras cidades também emitidas pela dsf 8 years ago
Felipe 4fc6b9dc89 adicionados urls de cidades do dsf 8 years ago
carcaroff f42937ae5f [FIX] 8 years ago
carcaroff 4915472aa7 [FIX] 8 years ago
carcaroff 0a21cfaad8 [FIX]Campo CPF 8 years ago
Fábio Luna 22ac348b8b Implementa NFse de Campinas 8 years ago
Fábio Luna c2e2d1ed46 [WIP] Implementa NFS-e Campinas 8 years ago
Danimar Ribeiro 5a85580ba4 [WIP] - NFSE Floripa 8 years ago
Felipe 2138bd3ee2 adicionado string.strip no recursive_normalize 8 years ago
Danimar Ribeiro 7cff7bb5f1 [WIP] Implementação da nfse de florianópolis 8 years ago
Fábio Luna 9219ec4243 [FIX] Corrige erro 225 na NF-e, devido a caracteres especiais. 8 years ago
Fábio Luna fa3f44b0de [FIX]Corrige envios de eventos 8 years ago
Danimar Ribeiro 914912ec7c Nova versão para publicar 8 years ago
Fábio Luna 582742ecca [FIX] Corrige consulta de distribuíção de NF-e 8 years ago
Danimar Ribeiro 9b21368d25 Nova versão, atualização de badges 8 years ago
Danimar Ribeiro 0b47eba7b5 Refactor - Permitir criar o xml de envio separadamente 8 years ago
Danimar Ribeiro b05bd19354 Build para windows 8 years ago
Danimar Ribeiro a1c65663bc Updating setup.py to include font files 8 years ago
Fábio Luna aa4fd2bb42 Inclui fonte NimbusSanL. 8 years ago
Danimar Ribeiro c44cd90103 Fixing build for windows 8 years ago
Danimar Ribeiro 7b31a2f78f Removendo print - Ajustando encodings 8 years ago
Danimar Ribeiro fabe0e0561 Preparando pypi package para versão python >= 3.4 8 years ago
Danimar Ribeiro 0c89b3e957 Modificação na estrutura do projeto - Movendo testes para fora do pacote 8 years ago
Danimar Ribeiro 5c5602acf9 Migração para python 3 - Correção de testes 8 years ago
Danimar Ribeiro 4581ea6dbe Ajuste de travis e dependencias da versão 3 8 years ago
  1. 2
      .gitignore
  2. 10
      .travis.yml
  3. 26
      README.md
  4. 7
      cidades/dsf.md
  5. 90
      pytrustnfe/Servidores.py
  6. 8
      pytrustnfe/__init__.py
  7. 13
      pytrustnfe/certificado.py
  8. 19
      pytrustnfe/client.py
  9. 159
      pytrustnfe/nfe/__init__.py
  10. 4
      pytrustnfe/nfe/assinatura.py
  11. 8
      pytrustnfe/nfe/comunicacao.py
  12. 460
      pytrustnfe/nfe/danfce.py
  13. 139
      pytrustnfe/nfe/danfe.py
  14. BIN
      pytrustnfe/nfe/fonts/NimbusSanL Bold.ttf
  15. BIN
      pytrustnfe/nfe/fonts/NimbusSanL Regular.ttf
  16. 4
      pytrustnfe/nfe/templates/NfeAutorizacao.xml
  17. 4
      pytrustnfe/nfe/templates/RecepcaoEventoCarta.xml
  18. 90
      pytrustnfe/nfse/assinatura.py
  19. 2
      pytrustnfe/nfse/betha/__init__.py
  20. 82
      pytrustnfe/nfse/bh/__init__.py
  21. 44
      pytrustnfe/nfse/bh/assinatura.py
  22. 13
      pytrustnfe/nfse/bh/templates/CancelarNfse.xml
  23. 11
      pytrustnfe/nfse/bh/templates/GerarNfse.xml
  24. 91
      pytrustnfe/nfse/bh/templates/Rps.xml
  25. 74
      pytrustnfe/nfse/carioca/__init__.py
  26. 13
      pytrustnfe/nfse/carioca/templates/CancelarNfse.xml
  27. 3
      pytrustnfe/nfse/carioca/templates/GerarNfse.xml
  28. 91
      pytrustnfe/nfse/carioca/templates/Rps.xml
  29. 131
      pytrustnfe/nfse/dsf/__init__.py
  30. 18
      pytrustnfe/nfse/dsf/templates/cancelar.xml
  31. 11
      pytrustnfe/nfse/dsf/templates/consulta_notas.xml
  32. 10
      pytrustnfe/nfse/dsf/templates/consultarLote.xml
  33. 22
      pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml
  34. 108
      pytrustnfe/nfse/dsf/templates/enviar.xml
  35. 12
      pytrustnfe/nfse/dsf/templates/soap_header.xml
  36. 119
      pytrustnfe/nfse/floripa/__init__.py
  37. 7
      pytrustnfe/nfse/floripa/templates/cancelar_nota.xml
  38. 41
      pytrustnfe/nfse/floripa/templates/processar_nota.xml
  39. 35
      pytrustnfe/nfse/ginfes/__init__.py
  40. 11
      pytrustnfe/nfse/imperial/__init__.py
  41. 82
      pytrustnfe/nfse/mga/__init__.py
  42. 44
      pytrustnfe/nfse/mga/assinatura.py
  43. 13
      pytrustnfe/nfse/mga/templates/CancelarNfse.xml
  44. 11
      pytrustnfe/nfse/mga/templates/GerarNfse.xml
  45. 91
      pytrustnfe/nfse/mga/templates/Rps.xml
  46. 13
      pytrustnfe/nfse/paulistana/__init__.py
  47. 2
      pytrustnfe/nfse/susesu/__init__.py
  48. 1
      pytrustnfe/test/XMLs/paulistana_resultado.xml
  49. 19
      pytrustnfe/utils.py
  50. 26
      pytrustnfe/xml/__init__.py
  51. 18
      pytrustnfe/xml/filters.py
  52. 0
      pytrustnfe/xml/schemas/enviNFe_v3.10.xsd
  53. 0
      pytrustnfe/xml/schemas/leiauteNFe_v3.10.xsd
  54. 0
      pytrustnfe/xml/schemas/nfe_v3.10.xsd
  55. 2
      pytrustnfe/xml/schemas/tiposBasico_v3.10.xsd
  56. 0
      pytrustnfe/xml/schemas/xmldsig-core-schema_v1.01.xsd
  57. 31
      pytrustnfe/xml/validate.py
  58. 24
      requirements.txt
  59. 32
      setup.py
  60. 0
      tests/XMLs/NFe00000857.xml
  61. 0
      tests/XMLs/jinja_remove_empty.xml
  62. 0
      tests/XMLs/jinja_result.xml
  63. 0
      tests/XMLs/jinja_template.xml
  64. 0
      tests/XMLs/paulistana_canc.xml
  65. 1
      tests/XMLs/paulistana_canc_errado.xml
  66. 1
      tests/XMLs/paulistana_canc_ok.xml
  67. 1
      tests/XMLs/paulistana_resultado.xml
  68. 16
      tests/XMLs/paulistana_signature.xml
  69. 0
      tests/XMLs/recibo_envio_1.xml
  70. 0
      tests/XMLs/recibo_envio_2.xml
  71. 0
      tests/XMLs/recibo_protocolo_sucesso_1.xml
  72. 0
      tests/XMLs/recibo_protocolo_sucesso_2.xml
  73. 0
      tests/__init__.py
  74. 6
      tests/test_add_qr_code.py
  75. 12
      tests/test_assinatura.py
  76. 8
      tests/test_certificado.py
  77. 0
      tests/test_comunicacao.py
  78. 2
      tests/test_consulta_cadastro.py
  79. 2
      tests/test_danfe.py
  80. 2
      tests/test_ginfes.py
  81. 8
      tests/test_nfse_paulistana.py
  82. 0
      tests/test_servidores.py
  83. 16
      tests/test_utils.py
  84. 4
      tests/test_xml.py
  85. 5
      tests/test_xml_serializacao.py
  86. 0
      tests/teste.pfx
  87. 0
      tests/xml_assinado.xml
  88. 6
      tests/xml_com_qrcode.xml
  89. 6
      tests/xml_sem_qrcode.xml
  90. 0
      tests/xml_valido_assinado.xml

2
.gitignore

@ -11,4 +11,4 @@ dist/
*egg*/
docs/_build
.vscode/tags
.cache/
.cache

10
.travis.yml

@ -1,13 +1,15 @@
dist: precise
language: python
python:
- '2.7'
- "3.4"
- "3.5"
- "3.6"
virtual_env:
system_site_packages: true
install:
- pip install --upgrade pip
- pip install -r requirements.txt
script: coverage run --source=pytrustnfe setup.py nosetests
script:
pytest --cov=pytrustnfe
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq python-dev libffi-dev libxml2-dev libxslt1-dev libssl-dev
@ -19,5 +21,5 @@ deploy:
password:
secure: wV+DH+WVji4qyCRXxugOsu8/u9MgUN9YggIBozh2Si1z6OlONZVr/oCaJDW8UD+Qg0EF87RbHuEmmlpAZVERAZv5uGsxjSO/NyvAsr99sOlTy9TSLi6TLp4aPnOCgjBTFDWkwkNyDTGYGNfendS7KO2jaHUsr/eDZcpTz42lOfDgpmccz822wwI6Uu1hNC61qlskPkKVzFhHT61/XAgmjHvw1wAMWVmv9/E6J8VAlZoI9/v3K0RTRisB/+0+sSvY86crYyuW/zIEhQJnMu/gfFWDSxNdY+0S3VyFgERn5S7IYlpBPUUlukX5aPXy+OQD2ygeu7w9f6aOSaJZsoyhe4pPXDjA9XNyfiazuZrz51fzhricMvdsMPAcukK/sJzGICAFgOutAjy+nGBkNqA2genKL8gMtJGUrPW5Yq5MGMC7FEgEQi5SgEj+01FgSY5mHlR3qo9bEgXWcxhNL/uZ3C1ElnGNLbyn5hjWzCnMEe70JwfWNQxGgtNm73vrrsZJ7M5wGjrEKVAvTERQegRQm2ObX7YsPmTY+tF15Hxs8GiZ0T/MzpxGe6yAkIutKI0CxpoUMXBnrmcMbn74GT8KWQjS724AA3K5ePO5ogLECxIq3huyB9USeeXmYBhUtcLpKSSH7gA/8vT/tvXK0+/YNTKzIIrOjuZ9IOVrwq2PyUY=
on:
branch: master
branch: master3
distributions: "bdist_wheel"

26
README.md

@ -1,37 +1,37 @@
# PyTrustNFe
Biblioteca Python que tem por objetivo enviar NFe, NFCe e NFSe no Brasil
[![Coverage Status](https://coveralls.io/repos/danimaribeiro/PyTrustNFe/badge.svg?branch=master)](https://coveralls.io/r/danimaribeiro/PyTrustNFe?branch=master)
[![Code Health](https://landscape.io/github/danimaribeiro/PyTrustNFe/master/landscape.svg?style=flat)](https://landscape.io/github/danimaribeiro/PyTrustNFe/master)
[![Build Status](https://travis-ci.org/danimaribeiro/PyTrustNFe.svg?branch=master)](https://travis-ci.org/danimaribeiro/PyTrustNFe)
[![PyPI version](https://badge.fury.io/py/PyTrustNFe.svg)](https://badge.fury.io/py/PyTrustNFe)
[![Coverage Status](https://coveralls.io/repos/danimaribeiro/PyTrustNFe/badge.svg?branch=master3)](https://coveralls.io/r/danimaribeiro/PyTrustNFe?branch=master3)
[![Code Health](https://landscape.io/github/danimaribeiro/PyTrustNFe/master3/landscape.svg?style=flat)](https://landscape.io/github/danimaribeiro/PyTrustNFe/master3)
[![Build Status](https://travis-ci.org/danimaribeiro/PyTrustNFe.svg?branch=master3)](https://travis-ci.org/danimaribeiro/PyTrustNFe)
[![PyPI version](https://badge.fury.io/py/PyTrustNFe3.svg)](https://badge.fury.io/py/PyTrustNFe3)
Dependências:
* PyXmlSec
* lxml
* signxml
* suds
* suds_requests
* suds-jurko
* suds-jurko-requests
* reportlab
* Jinja2
NFSe - Cidades atendidas
--------------
* [Ariss](cidades/ariss.md) - 4 cidades atendidas
* **Paulistana** - São Paulo/SP
* **Nota Carioca** - Rio de Janeiro/RJ
* **Imperial** - Petrópolis/RH
* [Susesu](cidades/susesu.md) - 3 cidades atendidas
* [Simpliss](cidades/simpliss.md) - 18 cidade atendidas
* [GINFES](cidaes/ginfes.md) - 79 cidades atendidas
* [DSF](cidades/dsf.md) - 7 cidades atendidas
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

7
cidades/dsf.md

@ -0,0 +1,7 @@
* Belém - PA
* Sorocaba - SP
* Teresina - PI
* Campinas - SP
* Uberlandia - MG
* São Luis - MA
* Campo Grande - MS

90
pytrustnfe/Servidores.py

@ -20,7 +20,6 @@ WS_NFCE_CADASTRO = 'NfeConsultaCadastro'
WS_NFCE_RECEPCAO_EVENTO = 'RecepcaoEventoCarta'
WS_NFCE_QR_CODE = 'NfeQRCode'
WS_NFCE_CONSULTA_DESTINADAS = 'NfeConsultaDest',
WS_NFCE_RET_AUTORIZACAO = 'NFeRetAutorizacao',
WS_NFE_CADASTRO = 'NfeConsultaCadastro'
@ -37,8 +36,8 @@ NFE_AMBIENTE_HOMOLOGACAO = 2
NFCE_AMBIENTE_PRODUCAO = 1
NFCE_AMBIENTE_HOMOLOGACAO = 2
NFE_MODELO = u'55'
NFCE_MODELO = u'65'
NFE_MODELO = '55'
NFCE_MODELO = '65'
SIGLA_ESTADO = {
'12': 'AC',
@ -97,11 +96,8 @@ def localizar_url(servico, estado, mod='55', ambiente=2):
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]
if 'https://' in complemento:
return complemento
return "https://%s/%s" % (dominio, complemento)
ws_qrcode = ESTADO_WS[sigla][NFCE_MODELO][ambiente][WS_NFCE_QR_CODE]
return ws_qrcode
METODO_WS = {
@ -137,27 +133,53 @@ METODO_WS = {
}
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',
WS_NFE_INUTILIZACAO: 'ws/nfeinutilizacao/nfeinutilizacao2.asmx',
WS_NFE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx',
WS_NFE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx',
NFE_MODELO: {
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',
WS_NFE_INUTILIZACAO: 'ws/nfeinutilizacao/nfeinutilizacao2.asmx',
WS_NFE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx',
WS_NFE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx',
},
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',
WS_NFE_INUTILIZACAO: 'ws/nfeinutilizacao/nfeinutilizacao2.asmx',
WS_NFE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx',
WS_NFE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx',
}
},
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',
WS_NFE_INUTILIZACAO: 'ws/nfeinutilizacao/nfeinutilizacao2.asmx',
WS_NFE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx',
WS_NFE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx',
NFCE_MODELO: {
NFCE_AMBIENTE_PRODUCAO: {
'servidor': 'nfce.svrs.rs.gov.br',
WS_NFCE_INUTILIZACAO: 'ws/nfeinutilizacao/nfeinutilizacao2.asmx',
WS_NFCE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx',
WS_NFCE_CANCELAMENTO: 'ws/recepcaoevento/recepcaoevento.asmx',
WS_NFCE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx',
WS_NFCE_AUTORIZACAO: 'ws/NfeAutorizacao/NFeAutorizacao.asmx',
WS_NFCE_RET_AUTORIZACAO: 'ws/NfeRetAutorizacao/NFeRetAutorizacao.asmx',
WS_NFCE_RECEPCAO_EVENTO: 'ws/recepcaoevento/recepcaoevento.asmx',
WS_NFCE_QR_CODE: 'http://dec.fazenda.df.gov.br/ConsultarNFCe.aspx',
},
NFCE_AMBIENTE_HOMOLOGACAO: {
'servidor': 'nfce-homologacao.svrs.rs.gov.br',
WS_NFCE_INUTILIZACAO: 'ws/nfeinutilizacao/nfeinutilizacao2.asmx',
WS_NFCE_CONSULTA: 'ws/NfeConsulta/NfeConsulta2.asmx',
WS_NFCE_CANCELAMENTO: 'ws/recepcaoevento/recepcaoevento.asmx',
WS_NFCE_SITUACAO: 'ws/NfeStatusServico/NfeStatusServico2.asmx',
WS_NFCE_AUTORIZACAO: 'ws/NfeAutorizacao/NFeAutorizacao.asmx',
WS_NFCE_RET_AUTORIZACAO: 'ws/NfeRetAutorizacao/NFeRetAutorizacao.asmx',
WS_NFCE_RECEPCAO_EVENTO: 'ws/recepcaoevento/recepcaoevento.asmx',
WS_NFCE_QR_CODE: 'http://dec.fazenda.df.gov.br/ConsultarNFCe.aspx',
}
}
}
@ -317,7 +339,7 @@ UFAM = {
WS_NFE_INUTILIZACAO: 'nfce-services-nac/services/NfeInutilizacao2',
WS_NFE_CONSULTA: 'nfce-services-nac/services/NfeConsulta2',
WS_NFE_SITUACAO: 'nfce-services-nac/services/NfeStatusServico2',
WS_NFCE_QR_CODE: 'nfceweb/consultarNFCe.jsp',
WS_NFCE_QR_CODE: 'http://homnfce.sefaz.am.gov.br/nfceweb/consultarNFCe.jsp',
}
}
}
@ -352,8 +374,8 @@ UFBA = {
UFCE = {
NFE_AMBIENTE_PRODUCAO: {
'servidor': 'nfe.sefaz.ce.gov.br',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeRecepcao2',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetRecepcao2',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeAutorizacao',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetAutorizacao',
WS_NFE_INUTILIZACAO: 'nfe2/services/NfeInutilizacao2',
WS_NFE_CONSULTA: 'nfe2/services/NfeConsulta2',
WS_NFE_SITUACAO: 'nfe2/services/NfeStatusServico2',
@ -363,8 +385,8 @@ UFCE = {
},
NFE_AMBIENTE_HOMOLOGACAO: {
'servidor': 'nfeh.sefaz.ce.gov.br',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeRecepcao2',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetRecepcao2',
WS_NFE_AUTORIZACAO: 'nfe2/services/NfeAutorizacao',
WS_NFE_RET_AUTORIZACAO: 'nfe2/services/NfeRetAutorizacao',
WS_NFE_INUTILIZACAO: 'nfe2/services/NfeInutilizacao2',
WS_NFE_CONSULTA: 'nfe2/services/NfeConsulta2',
WS_NFE_SITUACAO: 'nfe2/services/NfeStatusServico2',
@ -626,7 +648,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: 'https://homologacao.nfce.fazenda.sp.gov.br/NFCEConsultaPublica/Paginas/ConstultaQRCode.aspx',
}
}
}

8
pytrustnfe/__init__.py

@ -12,10 +12,10 @@ class HttpClient(object):
def _headers(self, action):
return {
u'Content-type':
u'text/xml; charset=utf-8;',
u'Accept': u'application/soap+xml; charset=utf-8',
u'SOAPAction': action
'Content-type':
'text/xml; charset=utf-8;',
'Accept': 'application/soap+xml; charset=utf-8',
'SOAPAction': action
}
def post_soap(self, xml_soap, action):

13
pytrustnfe/certificado.py

@ -2,8 +2,7 @@
# © 2016 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from uuid import uuid4
import tempfile
from OpenSSL import crypto
@ -13,8 +12,8 @@ class Certificado(object):
self.password = password
def save_pfx(self):
pfx_temp = '/tmp/' + uuid4().hex
arq_temp = open(pfx_temp, 'w')
pfx_temp = tempfile.mkstemp()[1]
arq_temp = open(pfx_temp, 'wb')
arq_temp.write(self.pfx)
arq_temp.close()
return pfx_temp
@ -28,12 +27,12 @@ def extract_cert_and_key_from_pfx(pfx, password):
# PEM formatted certificate
cert = crypto.dump_certificate(crypto.FILETYPE_PEM,
pfx.get_certificate())
return cert, key
return cert.decode(), key.decode()
def save_cert_key(cert, key):
cert_temp = '/tmp/' + uuid4().hex
key_temp = '/tmp/' + uuid4().hex
cert_temp = tempfile.mkstemp()[1]
key_temp = tempfile.mkstemp()[1]
arq_temp = open(cert_temp, 'w')
arq_temp.write(cert)

19
pytrustnfe/client.py

@ -42,16 +42,23 @@ class HttpClient(object):
self.cert_path = cert_path
self.key_path = key_path
def _headers(self, action):
def _headers(self, action, send_raw):
if send_raw:
return {
'Content-type': 'text/xml; charset=utf-8;',
'SOAPAction': "http://www.portalfiscal.inf.br/nfe/wsdl/%s" % action,
'Accept': 'application/soap+xml; charset=utf-8',
}
return {
u'Content-type': u'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s"' % action,
u'Accept': u'application/soap+xml; charset=utf-8',
'Content-type': 'application/soap+xml; charset=utf-8;',
'SOAPAction': 'http://www.portalfiscal.inf.br/nfe/wsdl/%s' % action,
}
def post_soap(self, xml_soap, cabecalho):
header = self._headers(cabecalho.soap_action)
def post_soap(self, xml_soap, cabecalho, send_raw):
header = self._headers(cabecalho.soap_action, send_raw)
urllib3.disable_warnings(category=InsecureRequestWarning)
res = requests.post(self.url, data=xml_soap,
res = requests.post(self.url, data=xml_soap.encode('utf-8'),
cert=(self.cert_path, self.key_path),
verify=False, headers=header)
return res.text

159
pytrustnfe/nfe/__init__.py

@ -5,6 +5,7 @@
import os
import hashlib
import binascii
from lxml import etree
from .comunicacao import executar_consulta
from .assinatura import Assinatura
@ -18,19 +19,21 @@ from pytrustnfe.exceptions import NFeValidationException
def _build_header(method, **kwargs):
action = {
'NfeAutorizacao': ('NfeAutorizacao', '3.10'),
'NfeRetAutorizacao': ('NfeRetAutorizacao', '3.10'),
'NfeConsultaCadastro': ('CadConsultaCadastro2', '2.00'),
'NfeInutilizacao': ('NfeInutilizacao2', '3.10'),
'RecepcaoEventoCancelamento': ('RecepcaoEvento', '1.00'),
'RecepcaoEventoCarta': ('RecepcaoEvento', '1.00'),
'NFeDistribuicaoDFe': ('NFeDistribuicaoDFe/nfeDistDFeInteresse',
'1.00'),
'RecepcaoEventoManifesto': ('RecepcaoEvento', '1.00'),
'NfeAutorizacao': ('NfeAutorizacao', '3.10', 'NfeAutorizacao/nfeAutorizacaoLote'),
'NfeRetAutorizacao': ('NfeRetAutorizacao', '3.10', 'NfeRetAutorizacao/nfeRetAutorizacaoLote'),
'NfeConsultaCadastro': ('CadConsultaCadastro2', '2.00', 'CadConsultaCadastro2/consultaCadastro2'),
'NfeInutilizacao': ('NfeInutilizacao2', '3.10', 'NfeInutilizacao2/nfeInutilizacaoNF2'),
'RecepcaoEventoCancelamento': ('RecepcaoEvento', '1.00', 'RecepcaoEvento/nfeRecepcaoEvento'),
'RecepcaoEventoCarta': ('RecepcaoEvento', '1.00', 'RecepcaoEvento/nfeRecepcaoEvento'),
'NFeDistribuicaoDFe': ('NFeDistribuicaoDFe/nfeDistDFeInteresse', '1.00', 'NFeDistribuicaoDFe/nfeDistDFeInteresse'),
'RecepcaoEventoManifesto': ('RecepcaoEvento', '1.00', 'RecepcaoEvento/nfeRecepcaoEvento'),
}
vals = {
'estado': kwargs['estado'],
'method': action[method][0],
'soap_action': action[method][2],
'versao': action[method][1]
}
vals = {'estado': kwargs['estado'],
'soap_action': action[method][0],
'versao': action[method][1]}
return CabecalhoSoap(**vals)
@ -80,15 +83,13 @@ def _add_qrCode(xml, **kwargs):
infnfesupl = etree.Element('infNFeSupl')
qrcode = etree.Element('qrCode')
chave_nfe = inf_nfe['Id'][3:]
dh_emissao = inf_nfe['ide']['dhEmi'].encode('hex')
dh_emissao = binascii.hexlify(inf_nfe['ide']['dhEmi'].encode()).decode()
versao = '100'
ambiente = kwargs['ambiente']
valor_total = inf_nfe['total']['vNF']
dest_cpf = 'Inexistente'
dest = nfe.find(".//{http://www.portalfiscal.inf.br/nfe}dest")
if dest:
dest_parent = dest.getparent()
dest_parent.remove(dest)
if inf_nfe.get('dest', False):
if inf_nfe['dest'].get('CPF', False):
dest_cpf = inf_nfe['dest']['CPF']
@ -96,11 +97,10 @@ def _add_qrCode(xml, **kwargs):
cpf = etree.Element('CPF')
cpf.text = dest_cpf
dest.append(cpf)
dest_parent.append(dest)
icms_total = inf_nfe['total']['vICMS']
dig_val = xml.find(
".//{http://www.w3.org/2000/09/xmldsig#}DigestValue")\
.text.encode('hex')
dig_val = binascii.hexlify(xml.find(
".//{http://www.w3.org/2000/09/xmldsig#}DigestValue").text.encode()).decode()
cid_token = kwargs['NFes'][0]['infNFe']['codigo_seguranca']['cid_token']
csc = kwargs['NFes'][0]['infNFe']['codigo_seguranca']['csc']
@ -108,7 +108,7 @@ def _add_qrCode(xml, **kwargs):
={5}&vICMS={6}&digVal={7}&cIdToken={8}{9}".\
format(chave_nfe, versao, ambiente, dest_cpf, dh_emissao,
valor_total, icms_total, dig_val, cid_token, csc)
c_hash_QR_code = hashlib.sha1(c_hash_QR_code).hexdigest()
c_hash_QR_code = hashlib.sha1(c_hash_QR_code.encode()).hexdigest()
QR_code_url = "?chNFe={0}&nVersao={1}&tpAmb={2}&{3}dhEmi={4}&vNF={5}&vICMS\
={6}&digVal={7}&cIdToken={8}&cHashQRCode={9}".\
@ -121,12 +121,13 @@ def _add_qrCode(xml, **kwargs):
qrcode.text = etree.CDATA(qrcode_text)
infnfesupl.append(qrcode)
nfe.insert(1, infnfesupl)
return etree.tostring(xml)
return etree.tostring(xml, encoding=str)
def _send(certificado, method, sign, **kwargs):
def _render(certificado, method, sign, **kwargs):
path = os.path.join(os.path.dirname(__file__), 'templates')
xmlElem_send = render_xml(path, '%s.xml' % method, True, **kwargs)
modelo = xmlElem_send.find(".//{http://www.portalfiscal.inf.br/nfe}mod")
modelo = modelo.text if modelo is not None else '55'
if modelo == '65':
@ -175,9 +176,13 @@ def _send(certificado, method, sign, **kwargs):
xml_send = _add_qrCode(xml_send, **kwargs)
else:
xml_send = etree.tostring(xmlElem_send)
xml_send = etree.tostring(xmlElem_send, encoding=str)
return xml_send
url = localizar_url(method, kwargs['estado'], modelo,
def _send(certificado, method, **kwargs):
xml_send = kwargs["xml"]
url = localizar_url(method, kwargs['estado'], kwargs['modelo'],
kwargs['ambiente'])
cabecalho = _build_header(method, **kwargs)
@ -189,55 +194,129 @@ def _send(certificado, method, sign, **kwargs):
send_raw=send_raw)
return {
'sent_xml': xml_send,
'received_xml': response,
'received_xml': response.decode(),
'object': obj
}
def autorizar_nfe(certificado, **kwargs): # Assinar
def xml_autorizar_nfe(certificado, **kwargs):
_generate_nfe_id(**kwargs)
return _send(certificado, 'NfeAutorizacao', True, **kwargs)
return _render(certificado, 'NfeAutorizacao', True, **kwargs)
def autorizar_nfe(certificado, **kwargs): # Assinar
if "xml" not in kwargs:
kwargs['xml'] = xml_autorizar_nfe(certificado, **kwargs)
return _send(certificado, 'NfeAutorizacao', **kwargs)
def xml_retorno_autorizar_nfe(certificado, **kwargs):
return _render(certificado, 'NfeRetAutorizacao', False, **kwargs)
def retorno_autorizar_nfe(certificado, **kwargs):
return _send(certificado, 'NfeRetAutorizacao', False, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_retorno_autorizar_nfe(certificado, **kwargs)
return _send(certificado, 'NfeRetAutorizacao', **kwargs)
def xml_recepcao_evento_cancelamento(certificado, **kwargs): # Assinar
return _render(certificado, 'RecepcaoEventoCancelamento', True, **kwargs)
def recepcao_evento_cancelamento(certificado, **kwargs): # Assinar
return _send(certificado, 'RecepcaoEventoCancelamento', True, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_recepcao_evento_cancelamento(certificado, **kwargs)
return _send(certificado, 'RecepcaoEventoCancelamento', **kwargs)
def xml_inutilizar_nfe(certificado, **kwargs):
return _render(certificado, 'NfeInutilizacao', True, **kwargs)
def inutilizar_nfe(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_inutilizar_nfe(certificado, **kwargs)
return _send(certificado, 'NfeInutilizacao', **kwargs)
def inutilizar_nfe(certificado, **kwargs): # Assinar
return _send(certificado, 'NfeInutilizacao', True, **kwargs)
def xml_consultar_protocolo_nfe(certificado, **kwargs):
return _render(certificado, 'NfeConsultaProtocolo', True, **kwargs)
def consultar_protocolo_nfe(certificado, **kwargs):
return _send(certificado, 'NfeConsultaProtocolo', True, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_consultar_protocolo_nfe(certificado, **kwargs)
return _send(certificado, 'NfeConsultaProtocolo', **kwargs)
def xml_nfe_status_servico(certificado, **kwargs):
return _render(certificado, 'NfeStatusServico', False, **kwargs)
def nfe_status_servico(certificado, **kwargs):
return _send(certificado, 'NfeStatusServico', False, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_nfe_status_servico(certificado, **kwargs)
return _send(certificado, 'NfeStatusServico', **kwargs)
def xml_consulta_cadastro(certificado, **kwargs):
return _render(certificado, 'NfeConsultaCadastro', False, **kwargs)
def consulta_cadastro(certificado, **kwargs):
return _send(certificado, 'NfeConsultaCadastro', False, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_consulta_cadastro(certificado, **kwargs)
kwargs['modelo'] = '55'
return _send(certificado, 'NfeConsultaCadastro', **kwargs)
def xml_recepcao_evento_carta_correcao(certificado, **kwargs): # Assinar
return _render(certificado, 'RecepcaoEventoCarta', True, **kwargs)
def recepcao_evento_carta_correcao(certificado, **kwargs): # Assinar
return _send(certificado, 'RecepcaoEventoCarta', True, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_recepcao_evento_carta_correcao(
certificado, **kwargs)
return _send(certificado, 'RecepcaoEventoCarta', **kwargs)
def xml_recepcao_evento_manifesto(certificado, **kwargs): # Assinar
return _render(certificado, 'RecepcaoEventoManifesto', True, **kwargs)
def recepcao_evento_manifesto(certificado, **kwargs): # Assinar
return _send(certificado, 'RecepcaoEventoManifesto', True, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_recepcao_evento_manifesto(certificado, **kwargs)
return _send(certificado, 'RecepcaoEventoManifesto', **kwargs)
def xml_recepcao_evento_epec(certificado, **kwargs): # Assinar
return _render(certificado, 'RecepcaoEventoEPEC', True, **kwargs)
def recepcao_evento_epec(certificado, **kwargs): # Assinar
return _send(certificado, 'RecepcaoEventoEPEC', True, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_recepcao_evento_epec(certificado, **kwargs)
return _send(certificado, 'RecepcaoEventoEPEC', **kwargs)
def xml_consulta_distribuicao_nfe(certificado, **kwargs): # Assinar
return _render(certificado, 'NFeDistribuicaoDFe', False, **kwargs)
def consulta_distribuicao_nfe(certificado, **kwargs):
return _send(certificado, 'NFeDistribuicaoDFe', False, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_consulta_distribuicao_nfe(certificado, **kwargs)
return _send(certificado, 'NFeDistribuicaoDFe', **kwargs)
def xml_download_nfe(certificado, **kwargs): # Assinar
return _render(certificado, 'NFeDistribuicaoDFe', False, **kwargs)
def download_nfe(certificado, **kwargs):
return _send(certificado, 'NFeDistribuicaoDFe', False, **kwargs)
if "xml" not in kwargs:
kwargs['xml'] = xml_download_nfe(certificado, **kwargs)
return _send(certificado, 'NFeDistribuicaoDFe', **kwargs)

4
pytrustnfe/nfe/assinatura.py

@ -32,7 +32,7 @@ class Assinatura(object):
ref_uri = ('#%s' % reference) if reference else None
signed_root = signer.sign(
xml_element, key=key, cert=cert,
xml_element, key=key.encode(), cert=cert.encode(),
reference_uri=ref_uri)
if reference:
element_signed = signed_root.find(".//*[@Id='%s']" % reference)
@ -42,4 +42,4 @@ class Assinatura(object):
if element_signed is not None and signature is not None:
parent = element_signed.getparent()
parent.append(signature)
return etree.tostring(signed_root)
return etree.tostring(signed_root, encoding=str)

8
pytrustnfe/nfe/comunicacao.py

@ -12,9 +12,9 @@ from ..xml import sanitize_response
def _soap_xml(body, cabecalho):
xml = '<?xml version="1.0" encoding="utf-8"?>'
xml += '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"><soap:Header>'
xml += '<nfeCabecMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.soap_action + '">'
xml += '<nfeCabecMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.method + '">'
xml += '<cUF>' + cabecalho.estado + '</cUF><versaoDados>' + cabecalho.versao + '</versaoDados></nfeCabecMsg></soap:Header><soap:Body>'
xml += '<nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.soap_action + '">'
xml += '<nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/' + cabecalho.method + '">'
xml += body
xml += '</nfeDadosMsg></soap:Body></soap:Envelope>'
return xml.rstrip('\n')
@ -30,5 +30,5 @@ def executar_consulta(certificado, url, cabecalho, xmlEnviar, send_raw=False):
if send_raw:
xml = '<?xml version="1.0" encoding="utf-8"?>' + xmlEnviar.rstrip('\n')
xml_enviar = xml
xml_retorno = client.post_soap(xml_enviar, cabecalho)
return sanitize_response(xml_retorno)
xml_retorno = client.post_soap(xml_enviar, cabecalho, send_raw)
return sanitize_response(xml_retorno.encode())

460
pytrustnfe/nfe/danfce.py

@ -0,0 +1,460 @@
# -*- coding: utf-8 -*-
# © 2017 Johny Chen Jy, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import re
from textwrap import wrap
from io import BytesIO
from reportlab.lib import utils
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm, mm
from reportlab.graphics.barcode import qr
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Drawing
from reportlab.platypus import Table, TableStyle, Paragraph, Image
from reportlab.lib.enums import TA_CENTER
from reportlab.lib.styles import ParagraphStyle
def format_cnpj_cpf(value):
if len(value) < 12: # CPF
cValue = '%s.%s.%s-%s' % (value[:-8], value[-8:-5],
value[-5:-2], value[-2:])
else:
cValue = '%s.%s.%s/%s-%s' % (value[:-12], value[-12:-9],
value[-9:-6], value[-6:-2], value[-2:])
return cValue
def getdateUTC(cDateUTC):
cDt = cDateUTC[0:10].split('-')
cDt.reverse()
return '/'.join(cDt), cDateUTC[11:16]
def format_number(cNumber, precision=0, group_sep='.', decimal_sep=','):
if cNumber:
number = float(cNumber)
return ("{:,." + str(precision) + "f}").format(number).\
replace(",", "X").replace(".", ",").replace("X", ".")
return ""
def tagtext(oNode=None, cTag=None):
try:
xpath = ".//{http://www.portalfiscal.inf.br/nfe}%s" % (cTag)
cText = oNode.find(xpath).text
except:
cText = ''
return cText
def get_image(path, width=1 * cm):
img = utils.ImageReader(path)
iw, ih = img.getSize()
aspect = ih / float(iw)
return Image(path, width=width, height=(width * aspect))
def format_telefone(telefone):
telefone = re.sub('[^0-9]', '', telefone)
if len(telefone) == 10:
telefone = '(%s) %s-%s' % (telefone[0:2],
telefone[2:6],
telefone[6:])
elif len(telefone) == 11:
telefone = '(%s) %s-%s' % (telefone[0:2],
telefone[2:7],
telefone[7:])
return telefone
class danfce(object):
def __init__(self, list_xml, logo=None):
self.current_font_size = 7
self.current_font_name = 'NimbusSanL-Regu'
self.max_height = 840
self.min_height = 1
self.min_width = 5
self.max_width = 200
self.current_height = 840
self.oPDF_IO = BytesIO()
self.canvas = canvas.Canvas(self.oPDF_IO, pagesize=(7.2 * cm, 30 * cm))
self.canvas.setTitle('DANFCE')
self.canvas.setLineWidth(.5)
self.canvas.setFont(self.current_font_name, self.current_font_size)
self.list_xml = list_xml
self.logo = logo
self.nfce_generate()
def ide_emit(self, oXML=None):
elem_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit")
# Razão Social emitente
nomeEmpresa = tagtext(oNode=elem_emit, cTag='xFant')
self.drawTitle(nomeEmpresa, 10)
if self.logo:
img = get_image(self.logo, width=10 * mm)
img.drawOn(self.canvas, 5, 830)
cEnd = tagtext(oNode=elem_emit, cTag="xNome") + '<br />'
cEnd += "CNPJ: %s " % (format_cnpj_cpf(
tagtext(oNode=elem_emit, cTag='CNPJ')))
cEnd += "IE: %s" % (tagtext(oNode=elem_emit, cTag="IE")) + '<br />'
cEnd += tagtext(oNode=elem_emit, cTag='xLgr') + ', ' + tagtext(
oNode=elem_emit, cTag='nro') + ' - '
cEnd += tagtext(oNode=elem_emit, cTag='xBairro') + '<br />' + tagtext(
oNode=elem_emit, cTag='xMun') + ' - '
cEnd += tagtext(oNode=elem_emit, cTag='UF') + ' - ' + tagtext(
oNode=elem_emit, cTag='CEP') + '<br />'
cEnd += 'Fone: ' + format_telefone(tagtext(
oNode=elem_emit, cTag='fone'))
self._drawCenteredParagraph(cEnd)
self.drawLine()
def danfce_information(self):
self.drawTitle(
"DANFE NFC-e - Documento Auxiliar da Nota Fiscal de",
7, 'NimbusSanL-Bold')
self.drawTitle("Consumidor Eletrônica", 7, 'NimbusSanL-Bold')
self.drawString(
"NFC-e não permite aproveitamento de crédito de ICMS", True)
self.drawLine()
def produtos(self, oXML=None, el_det=None, oPaginator=None,
list_desc=None, list_cod_prod=None):
rows = [['Cód', 'Descrição', 'Qtde', 'Un', 'Unit.', 'Total']]
colWidths = (25, 90, 15, 15, 25, 25)
rowHeights = [7]
for id in range(oPaginator[0], oPaginator[1]):
item = el_det[id]
el_prod = item.find(".//{http://www.portalfiscal.inf.br/nfe}prod")
cod = tagtext(oNode=el_prod, cTag='cProd')
descricao = tagtext(oNode=el_prod, cTag='xProd')
descricao = (descricao[:20] + '..') if len(descricao) > 20 else descricao
Un = tagtext(oNode=el_prod, cTag='uCom')
Un = (Un[:2]) if len(Un) > 2 else Un
qtde = format_number(tagtext(oNode=el_prod, cTag='qCom'),
precision=2)
vl_unit = format_number(tagtext(oNode=el_prod, cTag='vUnCom'),
precision=2)
vl_total = format_number(
tagtext(oNode=el_prod, cTag='vProd'), precision=2)
new_row = [cod, descricao, qtde, Un, vl_unit, vl_total]
rows.append(new_row)
rowHeights.append(self.current_font_size + 2)
self._draw_product_table(rows, colWidths, rowHeights)
def _draw_product_table(self, rows, colWidths, rowHeights):
table = Table(rows, colWidths, tuple(rowHeights))
table.setStyle(TableStyle([
('FONTSIZE', (0, 0), (-1, -1), 7),
('FONT', (0, 1), (-1, -1), 'NimbusSanL-Regu'),
('FONT', (0, 0), (-1, 0), 'NimbusSanL-Bold'),
('ALIGN', (0, 0), (-1, 0), "LEFT"),
('ALIGN', (1, 0), (-1, 0), "LEFT"),
('ALIGN', (2, 0), (-1, 0), "CENTER"),
('ALIGN', (3, 0), (-1, 0), "CENTER"),
('ALIGN', (0, 1), (-1, -1), "LEFT"),
('ALIGN', (1, 1), (-1, -1), "LEFT"),
('ALIGN', (2, 1), (-1, -1), "CENTER"),
('ALIGN', (3, 1), (-1, -1), "CENTER"),
]))
w, h = table.wrapOn(self.canvas, 200, 450)
table.drawOn(self.canvas, 0, self.current_height - (h * 1.2))
self.current_height -= (h * 1.1)
def totais(self, oXML=None):
# Impostos
el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total")
total_tributo = format_number(tagtext(oNode=el_total, cTag='vTotTrib'),
precision=2)
valor_total = format_number(tagtext(oNode=el_total, cTag='vProd'),
precision=2)
desconto = format_number(tagtext(oNode=el_total, cTag='vDesc'),
precision=2)
valor_a_pagar = format_number(tagtext(oNode=el_total, cTag='vNF'),
precision=2)
el_pag = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}pag")
troco = format_number(tagtext(oNode=el_pag, cTag="vTroco"))
payment_method_list = {'01': 'Dinheiro',
'02': 'Cheque',
'03': 'Cartão de Crédito',
'04': 'Cartão de Débito',
"05": "Crédito Loja",
'10': 'Vale Alimentação',
'11': 'Vale Refeição',
'12': 'Vale Presente',
'13': 'Vale Combustível',
'14': 'Duplicata Mercantil',
'15': 'Boleto Bancario',
'90': 'Sem Pagamento',
'99': 'Outros'}
quant_produtos = len(oXML.findall(
".//{http://www.portalfiscal.inf.br/nfe}det"))
payment_methods = []
for pagId, item in enumerate(el_pag):
if 'tPag' not in item.tag:
continue
payment = []
method = payment_method_list[item.text]
payment.append(method)
payment.append(format_number(item.getnext().text, precision=2))
payment_methods.append(payment)
values = {'quantidade_itens': quant_produtos,
'total_tributo': total_tributo,
'valor_total': valor_total,
'desconto': desconto,
'valor_a_pagar': valor_a_pagar,
'formas_de_pagamento': payment_methods,
'troco': troco,
}
self.draw_totals_table(values)
self.drawLine()
def draw_totals_table(self, values):
rowHeights = [7, 7, 7, 7, 10]
data = [['QTD.TOTAL DE ITENS', values['quantidade_itens']],
['VALOR TOTAL R$', values['valor_total']],
['DESCONTO R$', values['desconto']],
['VALOR A PAGAR R$', values['valor_a_pagar']],
['FORMA DE PAGAMENTO', 'VALOR PAGO R$'],
]
for item in values['formas_de_pagamento']:
data.append([item[0], item[1]])
rowHeights.append(7)
data.append(['TROCO', format_number(values['troco'], precision=2)])
rowHeights.append(7)
table2 = Table(data, colWidths=(150, 50), rowHeights=tuple(rowHeights))
table2.setStyle(TableStyle([
('FONTSIZE', (0, 0), (-1, -1), 7),
('FONT', (0, 0), (1, -1), 'NimbusSanL-Regu'),
('FONT', (0, 4), (1, 4), 'NimbusSanL-Bold'),
('ALIGN', (1, 0), (1, -1), "RIGHT")
]))
w, h = table2.wrapOn(self.canvas, 200, 450)
table2.drawOn(self.canvas, 0, self.current_height - (h * 1.1))
self.current_height -= h
def inf_authentication(self, oXML=None):
el_infNFe = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}infNFe")
# n nfce, serie e data de solicitacao
el_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
el_NFeSupl = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}infNFeSupl")
el_dest = el_infNFe.find(".//{http://www.portalfiscal.inf.br/nfe}dest")
# chave, n protocolo, data autorizacao
el_prot_nfe = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}protNFe")
el_infAdic = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}infAdic")
url_chave = tagtext(oNode=el_NFeSupl, cTag='urlChave')
access_key = tagtext(oNode=el_prot_nfe, cTag="chNFe")
frase_chave_acesso = 'Consulte pela Chave de Acesso em:<br />\
%s<br />%s' % (url_chave, access_key)
qrcode = tagtext(oNode=el_NFeSupl, cTag='qrCode')
cnpj = tagtext(oNode=el_dest, cTag='CNPJ')
cpf = tagtext(oNode=el_dest, cTag='CPF')
if cnpj:
cnpj_cpf = format_cnpj_cpf(cnpj)
cnpj_cpf = "CONSUMIDOR CNPJ: %s" % (cnpj)
elif cpf:
cnpj_cpf = format_cnpj_cpf(cpf)
cnpj_cpf = "CONSUMIDOR CPF: %s" % (cpf)
else:
cnpj_cpf = u"CONSUMIDOR NÃO IDENTIFICADO"
nNFC = tagtext(oNode=el_ide, cTag="nNF")
serie = tagtext(oNode=el_ide, cTag='serie')
dataSolicitacao = getdateUTC(tagtext(oNode=el_ide, cTag="dhEmi"))
dataSolicitacao = dataSolicitacao[0] + " " + dataSolicitacao[1]
numProtocolo = tagtext(oNode=el_prot_nfe, cTag="nProt")
dataAutorizacao = getdateUTC(tagtext(oNode=el_prot_nfe,
cTag='dhRecbto'))
dataAutorizacao = dataAutorizacao[0] + " " + dataAutorizacao[1]
text = u"%s <br />%s <br />NFC-e nº%s Série %s %s<br />\
Protocolo de autorização: %s<br />Data de autorização %s<br />\
" % (frase_chave_acesso, cnpj_cpf, nNFC, serie, dataSolicitacao,
numProtocolo, dataAutorizacao)
self._drawCenteredParagraph(text)
self.draw_qr_code(qrcode)
infAdFisco = tagtext(oNode=el_infAdic, cTag='infAdFisco')
self._drawCenteredParagraph(infAdFisco)
infCpl = tagtext(oNode=el_infAdic, cTag='infCpl')
self._drawCenteredParagraph(infCpl)
def _drawCenteredParagraph(self, text):
style = ParagraphStyle(
name='Normal',
fontName='NimbusSanL-Regu',
fontSize=7,
alignment=TA_CENTER,
leading=7,
)
paragraph = Paragraph(text, style=style)
w, h = paragraph.wrapOn(self.canvas, 180, 300)
paragraph.drawOn(self.canvas, 10, self.current_height - h)
self.current_height -= (h*1.1)
def drawString(self, string, centered=False):
if centered:
self.canvas.drawCentredString(
self.max_width / 2, self.current_height, string)
self.current_height -= self.current_font_size
else:
self.canvas.drawString(self.min_width, self.current_height, string)
self.current_height -= self.current_font_size
def drawTitle(self, string, size, font='NimbusSanL-Regu'):
self.canvas.setFont(font, size)
self.canvas.drawCentredString(
self.max_width / 2, self.current_height, string)
self.current_height -= self.current_font_size
self.canvas.setFont(self.current_font_name, self.current_font_size)
def drawLine(self):
self.canvas.line(self.min_width, self.current_height,
self.max_width, self.current_height)
self.current_height -= self.current_font_size
def draw_qr_code(self, string):
qr_code = qr.QrCodeWidget(string)
drawing = Drawing(23 * mm, 23 * mm)
drawing.add(qr_code)
renderPDF.draw(drawing, self.canvas, 20 * mm, self.current_height - 85)
self.current_height -= 85
def newpage(self):
self.current_height = self.max_height
self.Page += 1
self.canvas.showPage()
def nfce_generate(self):
for oXML in self.list_xml:
oXML_cobr = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}cobr")
self.NrPages = 1
self.Page = 1
# Calculando total linhas usadas para descrições dos itens
# Com bloco fatura, apenas 29 linhas para itens na primeira folha
nNr_Lin_Pg_1 = 34 if oXML_cobr is None else 30
# [ rec_ini , rec_fim , lines , limit_lines ]
oPaginator = [[0, 0, 0, nNr_Lin_Pg_1]]
el_det = oXML.findall(".//{http://www.portalfiscal.inf.br/nfe}det")
if el_det is not None:
list_desc = []
list_cod_prod = []
nPg = 0
for nId, item in enumerate(el_det):
el_prod = item.find(
".//{http://www.portalfiscal.inf.br/nfe}prod")
infAdProd = item.find(
".//{http://www.portalfiscal.inf.br/nfe}infAdProd")
list_ = wrap(tagtext(oNode=el_prod, cTag='xProd'), 56)
if infAdProd is not None:
list_.extend(wrap(infAdProd.text, 56))
list_desc.append(list_)
list_cProd = wrap(tagtext(oNode=el_prod, cTag='cProd'), 14)
list_cod_prod.append(list_cProd)
# Nr linhas necessárias p/ descrição item
nLin_Itens = len(list_)
if (oPaginator[nPg][2] + nLin_Itens) >= oPaginator[nPg][3]:
oPaginator.append([0, 0, 0, 77])
nPg += 1
oPaginator[nPg][0] = nId
oPaginator[nPg][1] = nId + 1
oPaginator[nPg][2] = nLin_Itens
else:
# adiciona-se 1 pelo funcionamento de xrange
oPaginator[nPg][1] = nId + 1
oPaginator[nPg][2] += nLin_Itens
self.NrPages = len(oPaginator) # Calculando nr. páginas
self.ide_emit(oXML=oXML)
# self.destinatario(oXML=oXML)
self.danfce_information()
self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPaginator[0],
list_desc=list_desc, list_cod_prod=list_cod_prod)
self.drawLine()
self.totais(oXML=oXML)
self.inf_authentication(oXML=oXML)
# Gera o restante das páginas do XML
for oPag in oPaginator[1:]:
if oPag:
self.newpage()
self.ide_emit(oXML=oXML)
# self.destinatario(oXML=oXML)
self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPag,
list_desc=list_desc,
list_cod_prod=list_cod_prod)
self.totais(oXML=oXML)
self.inf_authentication(oXML=oXML)
self.newpage()
self.canvas.save()
def writeto_pdf(self, fileObj):
pdf_out = self.oPDF_IO.getvalue()
self.oPDF_IO.close()
fileObj.write(pdf_out)

139
pytrustnfe/nfe/danfe.py

@ -3,8 +3,8 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# Classe para geração de PDF da DANFE a partir de xml etree.fromstring
from cStringIO import StringIO as IO
import os
from io import BytesIO
from textwrap import wrap
from reportlab.lib import utils
@ -17,6 +17,11 @@ from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.enums import TA_CENTER
from reportlab.platypus import Paragraph, Image
from reportlab.lib.styles import ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import pytz
from datetime import datetime, timedelta
def chunks(cString, nLen):
@ -34,10 +39,43 @@ def format_cnpj_cpf(value):
return cValue
def getdateUTC(cDateUTC):
cDt = cDateUTC[0:10].split('-')
def getdateByTimezone(cDateUTC, timezone=None):
'''
Esse método trata a data recebida de acordo com o timezone do
usuário. O seu retorno é dividido em duas partes:
1) A data em si;
2) As horas;
:param cDateUTC: string contendo as informações da data
:param timezone: timezone do usuário do sistema
:return: data e hora convertidos para a timezone do usuário
'''
# Aqui cortamos a informação do timezone da string (+03:00)
dt = cDateUTC[0:19]
# Verificamos se a string está completa (data + hora + timezone)
if timezone and len(cDateUTC) == 25:
# tz irá conter informações da timezone contida em cDateUTC
tz = cDateUTC[19:25]
tz = int(tz.split(':')[0])
dt = datetime.strptime(dt, '%Y-%m-%dT%H:%M:%S')
# dt agora será convertido para o horario em UTC
dt = dt - timedelta(hours=tz)
# tzinfo passará a apontar para <UTC>
dt = pytz.utc.localize(dt)
# valor de dt é convertido para a timezone do usuário
dt = timezone.normalize(dt)
dt = dt.strftime('%Y-%m-%dT%H:%M:%S')
cDt = dt[0:10].split('-')
cDt.reverse()
return '/'.join(cDt), cDateUTC[11:16]
return '/'.join(cDt), dt[11:16]
def format_number(cNumber):
@ -72,7 +110,16 @@ def get_image(path, width=1 * cm):
class danfe(object):
def __init__(self, sizepage=A4, list_xml=None, recibo=True,
orientation='portrait', logo=None, cce_xml=None):
orientation='portrait', logo=None, cce_xml=None,
timezone=None):
path = os.path.join(os.path.dirname(__file__), 'fonts')
pdfmetrics.registerFont(
TTFont('NimbusSanL-Regu',
os.path.join(path, 'NimbusSanL Regular.ttf')))
pdfmetrics.registerFont(
TTFont('NimbusSanL-Bold',
os.path.join(path, 'NimbusSanL Bold.ttf')))
self.width = 210 # 21 x 29,7cm
self.height = 297
self.nLeft = 10
@ -86,7 +133,7 @@ class danfe(object):
'2': '2 - Terceiros',
'9': '9 - Sem Frete'}
self.oPDF_IO = IO()
self.oPDF_IO = BytesIO()
if orientation == 'landscape':
raise NameError('Rotina não implementada')
else:
@ -104,8 +151,8 @@ class danfe(object):
self.Page = 1
# Calculando total linhas usadas para descrições dos itens
# Com bloco fatura, apenas 29 linhas para itens na primeira folha
nNr_Lin_Pg_1 = 34 if oXML_cobr is None else 30
# Com bloco fatura, apenas 25 linhas para itens na primeira folha
nNr_Lin_Pg_1 = 30 if oXML_cobr is None else 26
# [ rec_ini , rec_fim , lines , limit_lines ]
oPaginator = [[0, 0, 0, nNr_Lin_Pg_1]]
el_det = oXML.findall(".//{http://www.portalfiscal.inf.br/nfe}det")
@ -144,13 +191,13 @@ class danfe(object):
self.NrPages = len(oPaginator) # Calculando nr. páginas
if recibo:
self.recibo_entrega(oXML=oXML)
self.recibo_entrega(oXML=oXML, timezone=timezone)
self.ide_emit(oXML=oXML)
self.destinatario(oXML=oXML)
self.ide_emit(oXML=oXML, timezone=timezone)
self.destinatario(oXML=oXML, timezone=timezone)
if oXML_cobr is not None:
self.faturas(oXML=oXML_cobr)
self.faturas(oXML=oXML_cobr, timezone=timezone)
self.impostos(oXML=oXML)
self.transportes(oXML=oXML)
@ -162,7 +209,7 @@ class danfe(object):
# Gera o restante das páginas do XML
for oPag in oPaginator[1:]:
self.newpage()
self.ide_emit(oXML=oXML)
self.ide_emit(oXML=oXML, timezone=timezone)
self.produtos(oXML=oXML, el_det=el_det, oPaginator=oPag,
list_desc=list_desc, nHeight=77,
list_cod_prod=list_cod_prod)
@ -170,17 +217,19 @@ class danfe(object):
self.newpage()
if cce_xml:
for xml in cce_xml:
self._generate_cce(cce_xml=xml, oXML=oXML)
self._generate_cce(cce_xml=xml, oXML=oXML, timezone=timezone)
self.newpage()
self.canvas.save()
def ide_emit(self, oXML=None):
def ide_emit(self, oXML=None, timezone=None):
elem_infNFe = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}infNFe")
elem_protNFe = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}protNFe")
elem_emit = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}emit")
elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
elem_evento = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}infEvento")
cChave = elem_infNFe.attrib.get('Id')[3:]
barcode128 = code128.Code128(
@ -212,7 +261,7 @@ class danfe(object):
cNF = '{0:011,}'.format(int(cNF)).replace(",", ".")
self.stringcenter(self.nLeft + 100, self.nlin + 25, "%s" % (cNF))
self.stringcenter(self.nLeft + 100, self.nlin + 29, u"SÉRIE %s" % (
self.stringcenter(self.nLeft + 100, self.nlin + 29, "SÉRIE %s" % (
tagtext(oNode=elem_ide, cTag='serie')))
cPag = "Página %s de %s" % (str(self.Page), str(self.NrPages))
self.stringcenter(self.nLeft + 100, self.nlin + 32, cPag)
@ -250,7 +299,8 @@ class danfe(object):
self.stringcenter(self.nLeft + 116.5 + nW_Rect, self.nlin + 19.5,
' '.join(chunks(cChave, 4))) # Chave
self.canvas.setFont('NimbusSanL-Regu', 8)
cDt, cHr = getdateUTC(tagtext(oNode=elem_protNFe, cTag='dhRecbto'))
cDt, cHr = getdateByTimezone(
tagtext(oNode=elem_protNFe, cTag='dhRecbto'), timezone)
cProtocolo = tagtext(oNode=elem_protNFe, cTag='nProt')
cDt = cProtocolo + ' - ' + cDt + ' ' + cHr
nW_Rect = (self.width - self.nLeft - self.nRight - 110) / 2
@ -271,9 +321,9 @@ class danfe(object):
# Razão Social emitente
P = Paragraph(tagtext(oNode=elem_emit, cTag='xNome'), styleN)
w, h = P.wrap(55 * mm, 50 * mm)
w, h = P.wrap(55 * mm, 40 * mm)
P.drawOn(self.canvas, (self.nLeft + 30) * mm,
(self.height - self.nlin - 12) * mm)
(self.height - self.nlin - ((5*h + 12)/12)) * mm)
if self.logo:
img = get_image(self.logo, width=2 * cm)
@ -289,7 +339,7 @@ class danfe(object):
oNode=elem_emit, cTag='CEP')
regime = tagtext(oNode=elem_emit, cTag='CRT')
cEnd += u'<br />Regime Tributário: %s' % (REGIME_TRIBUTACAO[regime])
cEnd += '<br />Regime Tributário: %s' % (REGIME_TRIBUTACAO[regime])
styleN.fontName = 'NimbusSanL-Regu'
styleN.fontSize = 7
@ -308,9 +358,18 @@ class danfe(object):
self.string(self.nLeft + 65, 449, 'SEM VALOR FISCAL')
self.canvas.restoreState()
# Cancelado
if tagtext(oNode=elem_evento, cTag='cStat') == '135':
self.canvas.saveState()
self.canvas.rotate(45)
self.canvas.setFont('NimbusSanL-Bold', 60)
self.canvas.setFillColorRGB(1, 0.2, 0.2)
self.string(self.nLeft + 80, 275, 'CANCELADO')
self.canvas.restoreState()
self.nlin += 48
def destinatario(self, oXML=None):
def destinatario(self, oXML=None, timezone=None):
elem_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
elem_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest")
nMr = self.width - self.nRight
@ -354,9 +413,11 @@ class danfe(object):
else:
cnpj_cpf = format_cnpj_cpf(tagtext(oNode=elem_dest, cTag='CPF'))
self.string(nMr - 69, self.nlin + 7.5, cnpj_cpf)
cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhEmi'))
cDt, cHr = getdateByTimezone(tagtext(oNode=elem_ide, cTag='dhEmi'),
timezone)
self.string(nMr - 24, self.nlin + 7.7, cDt + ' ' + cHr)
cDt, cHr = getdateUTC(tagtext(oNode=elem_ide, cTag='dhSaiEnt'))
cDt, cHr = getdateByTimezone(
tagtext(oNode=elem_ide, cTag='dhSaiEnt'), timezone)
self.string(nMr - 24, self.nlin + 14.3, cDt + ' ' + cHr) # Dt saída
cEnd = tagtext(oNode=elem_dest, cTag='xLgr') + ', ' + tagtext(
oNode=elem_dest, cTag='nro')
@ -376,7 +437,7 @@ class danfe(object):
self.nlin += 24 # Nr linhas ocupadas pelo bloco
def faturas(self, oXML=None):
def faturas(self, oXML=None, timezone=None):
nMr = self.width - self.nRight
@ -409,7 +470,8 @@ class danfe(object):
line_iter = iter(oXML[1:10]) # Salta elemt 1 e considera os próximos 9
for oXML_dup in line_iter:
cDt, cHr = getdateUTC(tagtext(oNode=oXML_dup, cTag='dVenc'))
cDt, cHr = getdateByTimezone(tagtext(oNode=oXML_dup, cTag='dVenc'),
timezone)
self.string(self.nLeft + nCol + 1, self.nlin + nLin,
tagtext(oNode=oXML_dup, cTag='nDup'))
self.string(self.nLeft + nCol + 17, self.nlin + nLin, cDt)
@ -522,6 +584,8 @@ obsCont[@xCampo='NomeVendedor']")
def transportes(self, oXML=None):
el_transp = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}transp")
veic_transp = oXML.find(
".//{http://www.portalfiscal.inf.br/nfe}veicTransp")
nMr = self.width - self.nRight
self.canvas.setFont('NimbusSanL-Bold', 7)
@ -565,6 +629,12 @@ obsCont[@xCampo='NomeVendedor']")
tagtext(oNode=el_transp, cTag='xNome')[:40])
self.string(self.nLeft + 71, self.nlin + 7.7,
self.oFrete[tagtext(oNode=el_transp, cTag='modFrete')])
self.string(self.nLeft + 99, self.nlin + 7.7,
tagtext(oNode=el_transp, cTag='RNTC'))
self.string(self.nLeft + 116, self.nlin + 7.7,
tagtext(oNode=el_transp, cTag='placa'))
self.string(self.nLeft + 142, self.nlin + 7.7,
tagtext(oNode=veic_transp, cTag='UF'))
self.string(nMr - 39, self.nlin + 7.7,
format_cnpj_cpf(tagtext(oNode=el_transp, cTag='CNPJ')))
self.string(self.nLeft + 1, self.nlin + 14.2,
@ -646,7 +716,7 @@ obsCont[@xCampo='NomeVendedor']")
self.canvas.setFont('NimbusSanL-Regu', 5)
nLin = self.nlin + 10.5
for id in xrange(oPaginator[0], oPaginator[1]):
for id in range(oPaginator[0], oPaginator[1]):
item = el_det[id]
el_prod = item.find(".//{http://www.portalfiscal.inf.br/nfe}prod")
el_imp = item.find(
@ -737,7 +807,7 @@ obsCont[@xCampo='NomeVendedor']")
P.drawOn(self.canvas, (self.nLeft + 1) * mm, altura - h)
self.nlin += 36
def recibo_entrega(self, oXML=None):
def recibo_entrega(self, oXML=None, timezone=None):
el_ide = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}ide")
el_dest = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}dest")
el_total = oXML.find(".//{http://www.portalfiscal.inf.br/nfe}total")
@ -767,9 +837,10 @@ obsCont[@xCampo='NomeVendedor']")
self.string(self.width - self.nRight - nW +
2, self.nlin + 8, "%s" % (cNF))
self.string(self.width - self.nRight - nW + 2, self.nlin + 14,
u"SÉRIE %s" % (tagtext(oNode=el_ide, cTag='serie')))
"SÉRIE %s" % (tagtext(oNode=el_ide, cTag='serie')))
cDt, cHr = getdateUTC(tagtext(oNode=el_ide, cTag='dhEmi'))
cDt, cHr = getdateByTimezone(
tagtext(oNode=el_ide, cTag='dhEmi'), timezone)
cTotal = format_number(tagtext(oNode=el_total, cTag='vNF'))
cEnd = tagtext(oNode=el_dest, cTag='xNome') + ' - '
@ -779,7 +850,7 @@ obsCont[@xCampo='NomeVendedor']")
oNode=el_dest, cTag='xMun') + ' - '
cEnd += tagtext(oNode=el_dest, cTag='UF')
cString = u"""
cString = """
RECEBEMOS DE %s OS PRODUTOS/SERVIÇOS CONSTANTES DA NOTA FISCAL INDICADA
ABAIXO. EMISSÃO: %s VALOR TOTAL: %s
DESTINATARIO: %s""" % (tagtext(oNode=el_emit, cTag='xNome'),
@ -836,7 +907,7 @@ obsCont[@xCampo='NomeVendedor']")
self.oPDF_IO.close()
fileObj.write(pdf_out)
def _generate_cce(self, cce_xml=None, oXML=None):
def _generate_cce(self, cce_xml=None, oXML=None, timezone=None):
self.canvas.setLineWidth(.2)
# labels
@ -875,8 +946,8 @@ obsCont[@xCampo='NomeVendedor']")
self.string(82, 24, cnpj)
chave_acesso = tagtext(oNode=elem_infNFe, cTag='chNFe')
self.string(82, 30, chave_acesso)
data_correcao = getdateUTC(tagtext(
oNode=elem_infNFe, cTag='dhEvento'))
data_correcao = getdateByTimezone(tagtext(
oNode=elem_infNFe, cTag='dhEvento'), timezone)
data_correcao = data_correcao[0] + " " + data_correcao[1]
self.string(82, 36, data_correcao)
cce_id = elem_infNFe.values()[0]

BIN
pytrustnfe/nfe/fonts/NimbusSanL Bold.ttf

BIN
pytrustnfe/nfe/fonts/NimbusSanL Regular.ttf

4
pytrustnfe/nfe/templates/NfeAutorizacao.xml

@ -491,7 +491,6 @@
</IPINT>
{% endif %}
</IPI>
{% endif %}
{% if imposto.II is defined %}
<II>
<vBC>{{ imposto.II.vBC }}</vBC>
@ -500,6 +499,7 @@
<vIOF>{{ imposto.II.vIOF }}</vIOF>
</II>
{% endif %}
{% endif %}
<PIS>
{% if imposto.PIS.CST in ('01', '02') %}
<PISAliq>
@ -608,7 +608,7 @@
<ICMSUFDest>
<vBCUFDest>{{ imposto.ICMSUFDest.vBCUFDest }}</vBCUFDest>
<pFCPUFDest>{{ imposto.ICMSUFDest.pFCPUFDest }}</pFCPUFDest>
<pICMSUFDest>{{ imposto.ICMSUFDest.pICMSInter }}</pICMSUFDest>
<pICMSUFDest>{{ imposto.ICMSUFDest.pICMSUFDest }}</pICMSUFDest>
<pICMSInter>{{ imposto.ICMSUFDest.pICMSInter }}</pICMSInter>
<pICMSInterPart>{{ imposto.ICMSUFDest.pICMSInterPart }}</pICMSInterPart>
<vFCPUFDest>{{ imposto.ICMSUFDest.vFCPUFDest }}</vFCPUFDest>

4
pytrustnfe/nfe/templates/RecepcaoEventoCarta.xml

@ -12,9 +12,9 @@
<nSeqEvento>{{ nSeqEvento }}</nSeqEvento>
<verEvento>1.00</verEvento>
<detEvento versao="1.00">
<descEvento>Carta de Correção</descEvento>
<descEvento>Carta de Correcao</descEvento>
<xCorrecao>{{ xCorrecao|normalize|escape }}</xCorrecao>
<xCondUso>A Carta de Correção é disciplinada pelo § 1º-A do art. 7º do Convênio S/N, de 15 de dezembro de 1970 e pode ser utilizada para regularização de erro ocorrido na emissão de documento fiscal, desde que o erro não esteja relacionado com: I - as variáveis que determinam o valor do imposto tais como: base de cálculo, alíquota, diferença de preço, quantidade, valor da operação ou da prestação; II - a correção de dados cadastrais que implique mudança do remetente ou do destinatário; III - a data de emissão ou de saída.</xCondUso>
<xCondUso>A Carta de Correcao e disciplinada pelo paragrafo 1o-A do art. 7o do Convenio S/N, de 15 de dezembro de 1970 e pode ser utilizada para regularizacao de erro ocorrido na emissao de documento fiscal, desde que o erro nao esteja relacionado com: I - as variaveis que determinam o valor do imposto tais como: base de calculo, aliquota, diferenca de preco, quantidade, valor da operacao ou da prestacao; II - a correcao de dados cadastrais que implique mudanca do remetente ou do destinatario; III - a data de emissao ou de saida.</xCondUso>
</detEvento>
</infEvento>
</evento>

90
pytrustnfe/nfse/assinatura.py

@ -2,86 +2,52 @@
# © 2016 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from lxml import etree
import xmlsec
import libxml2
import os.path
consts = xmlsec.constants
NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#'
class Assinatura(object):
def __init__(self, arquivo, senha):
self.arquivo = arquivo
self.senha = senha
def __init__(self, cert_pem, private_key, password):
self.cert_pem = cert_pem
self.private_key = private_key
self.password = password
def _checar_certificado(self):
if not os.path.isfile(self.arquivo):
if not os.path.isfile(self.private_key):
raise Exception('Caminho do certificado não existe.')
def _inicializar_cripto(self):
libxml2.initParser()
libxml2.substituteEntitiesDefault(1)
xmlsec.init()
xmlsec.cryptoAppInit(None)
xmlsec.cryptoInit()
def _finalizar_cripto(self):
xmlsec.cryptoShutdown()
xmlsec.cryptoAppShutdown()
xmlsec.shutdown()
libxml2.cleanupParser()
def assina_xml(self, xml, reference):
self._checar_certificado()
self._inicializar_cripto()
try:
doc_xml = libxml2.parseMemory(
xml, len(xml))
signNode = xmlsec.TmplSignature(doc_xml,
xmlsec.transformInclC14NId(),
xmlsec.transformRsaSha1Id(), None)
doc_xml.getRootElement().addChild(signNode)
refNode = signNode.addReference(xmlsec.transformSha1Id(),
None, reference, None)
template = etree.fromstring(xml)
refNode.addTransform(xmlsec.transformEnvelopedId())
refNode.addTransform(xmlsec.transformInclC14NId())
keyInfoNode = signNode.ensureKeyInfo()
keyInfoNode.addX509Data()
key = xmlsec.Key.from_file(
self.private_key, format=xmlsec.constants.KeyDataFormatPem,
password=self.password)
dsig_ctx = xmlsec.DSigCtx()
chave = xmlsec.cryptoAppKeyLoad(filename=str(self.arquivo),
format=xmlsec.KeyDataFormatPkcs12,
pwd=str(self.senha),
pwdCallback=None,
pwdCallbackCtx=None)
signature_node = xmlsec.template.create(
template, c14n_method=consts.TransformInclC14N,
sign_method=consts.TransformRsaSha1)
template.append(signature_node)
ref = xmlsec.template.add_reference(
signature_node, consts.TransformSha1, uri='')
dsig_ctx.signKey = chave
dsig_ctx.sign(signNode)
xmlsec.template.add_transform(ref, consts.TransformEnveloped)
xmlsec.template.add_transform(ref, consts.TransformInclC14N)
status = dsig_ctx.status
dsig_ctx.destroy()
ki = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(ki)
if status != xmlsec.DSigStatusSucceeded:
raise RuntimeError(
'Erro ao realizar a assinatura do arquivo; status: "' +
str(status) +
'"')
ctx = xmlsec.SignatureContext()
ctx.key = key
xpath = doc_xml.xpathNewContext()
xpath.xpathRegisterNs('sig', NAMESPACE_SIG)
certificados = xpath.xpathEval(
'//sig:X509Data/sig:X509Certificate')
for i in range(len(certificados) - 1):
certificados[i].unlinkNode()
certificados[i].freeNode()
ctx.key.load_cert_from_file(
self.cert_pem, consts.KeyDataFormatPem)
xml = doc_xml.serialize()
return xml
finally:
doc_xml.freeDoc()
ctx.sign(signature_node)
return etree.tostring(template, encoding=str)

2
pytrustnfe/nfse/betha/__init__.py

@ -50,7 +50,7 @@ def _send(certificado, method, **kwargs):
try:
response = getattr(client.service, method)(1, xml_send)
except suds.WebFault, e:
except suds.WebFault as e:
return {
'sent_xml': xml_send,
'received_xml': e.fault.faultstring,

82
pytrustnfe/nfse/bh/__init__.py

@ -0,0 +1,82 @@
# © 2018 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
from lxml import etree
from requests import Session
from zeep import Client
from zeep.transports import Transport
from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key
from pytrustnfe.xml import render_xml, sanitize_response
from pytrustnfe.nfse.bh.assinatura import Assinatura
def _render(certificado, method, **kwargs):
path = os.path.join(os.path.dirname(__file__), 'templates')
xml_send = render_xml(path, '%s.xml' % method, True, **kwargs)
reference = ''
if method == 'GerarNfse':
reference = 'rps:%s' % kwargs['rps']['numero']
ref_lote = 'lote%s' % kwargs['rps']['numero_lote']
elif method == 'CancelarNfse':
reference = 'Cancelamento_NF%s' % kwargs['cancelamento']['numero_nfse']
signer = Assinatura(certificado.pfx, certificado.password)
xml_send = signer.assina_xml(xml_send, reference)
xml_send = signer.assina_xml(etree.fromstring(xml_send), ref_lote)
return xml_send.encode('utf-8')
def _send(certificado, method, **kwargs):
base_url = ''
if kwargs['ambiente'] == 'producao':
base_url = 'https://bhissdigital.pbh.gov.br/bhiss-ws/nfse?wsdl'
else:
base_url = 'https://bhisshomologa.pbh.gov.br/bhiss-ws/nfse?wsdl'
xml_send = kwargs["xml"].decode('utf-8')
xml_cabecalho = '<?xml version="1.0" encoding="UTF-8"?>\
<cabecalho xmlns="http://www.abrasf.org.br/nfse.xsd" versao="1.00">\
<versaoDados>1.00</versaoDados></cabecalho>'
cert, key = extract_cert_and_key_from_pfx(
certificado.pfx, certificado.password)
cert, key = save_cert_key(cert, key)
session = Session()
session.cert = (cert, key)
session.verify = False
transport = Transport(session=session)
client = Client(base_url, transport=transport)
response = client.service[method](xml_cabecalho, xml_send)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': xml_send,
'received_xml': response.decode('utf-8'),
'object': obj
}
def xml_gerar_nfse(certificado, **kwargs):
return _render(certificado, 'GerarNfse', **kwargs)
def gerar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_gerar_nfse(certificado, **kwargs)
return _send(certificado, 'GerarNfse', **kwargs)
def xml_cancelar_nfse(certificado, **kwargs):
return _render(certificado, 'CancelarNfse', **kwargs)
def cancelar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_cancelar_nfse(certificado, **kwargs)
return _send(certificado, 'CancelarNfse', **kwargs)

44
pytrustnfe/nfse/bh/assinatura.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# © 2016 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import signxml
from lxml import etree
from pytrustnfe.certificado import extract_cert_and_key_from_pfx
from signxml import XMLSigner
class Assinatura(object):
def __init__(self, arquivo, senha):
self.arquivo = arquivo
self.senha = senha
def assina_xml(self, xml_element, reference):
cert, key = extract_cert_and_key_from_pfx(self.arquivo, self.senha)
for element in xml_element.iter("*"):
if element.text is not None and not element.text.strip():
element.text = None
signer = XMLSigner(
method=signxml.methods.enveloped, signature_algorithm="rsa-sha1",
digest_algorithm='sha1',
c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315')
ns = {}
ns[None] = signer.namespaces['ds']
signer.namespaces = ns
ref_uri = ('#%s' % reference) if reference else None
signed_root = signer.sign(
xml_element, key=key.encode(), cert=cert.encode(),
reference_uri=ref_uri)
if reference:
element_signed = signed_root.find(".//*[@Id='%s']" % reference)
signature = signed_root.find(".//*[@URI='#%s']" % reference).getparent().getparent()
if element_signed is not None and signature is not None:
parent = element_signed.getparent()
parent.append(signature)
return etree.tostring(signed_root, encoding=str)

13
pytrustnfe/nfse/bh/templates/CancelarNfse.xml

@ -0,0 +1,13 @@
<CancelarNfseEnvio xmlns="http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd">
<Pedido xmlns="http://www.abrasf.org.br/nfse.xsd">
<InfPedidoCancelamento Id="pedidoCancelamento_{{ cancelamento.numero_nfse }}">
<IdentificacaoNfse>
<Numero>{{ cancelamento.numero_nfse }}</Numero>
<Cnpj>{{ cancelamento.cnpj_prestador }}</Cnpj>
<InscricaoMunicipal>{{ cancelamento.inscricao_municipal }}</InscricaoMunicipal>
<CodigoMunicipio>{{ cancelamento.cidade }}</CodigoMunicipio>
</IdentificacaoNfse>
<CodigoCancelamento>1</CodigoCancelamento>
</InfPedidoCancelamento>
</Pedido>
</CancelarNfseEnvio>

11
pytrustnfe/nfse/bh/templates/GerarNfse.xml

@ -0,0 +1,11 @@
<GerarNfseEnvio xmlns="http://www.abrasf.org.br/nfse.xsd">
<LoteRps Id="lote{{ rps.numero_lote }}" versao="1.00">
<NumeroLote>{{ rps.numero_lote }}</NumeroLote>
<Cnpj>{{ rps.prestador.cnpj }}</Cnpj>
<InscricaoMunicipal>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipal>
<QuantidadeRps>1</QuantidadeRps>
<ListaRps xmlns="http://www.abrasf.org.br/nfse.xsd">
{% include 'Rps.xml' %}
</ListaRps>
</LoteRps>
</GerarNfseEnvio>

91
pytrustnfe/nfse/bh/templates/Rps.xml

@ -0,0 +1,91 @@
<Rps>
<InfRps xmlns="http://www.abrasf.org.br/nfse.xsd" Id="rps:{{ rps.numero }}">
<IdentificacaoRps>
<Numero>{{ rps.numero }}</Numero>
<Serie>{{ rps.serie }}</Serie>
<Tipo>{{ rps.tipo_rps }}</Tipo>
</IdentificacaoRps>
<DataEmissao>{{ rps.data_emissao }}</DataEmissao>
<NaturezaOperacao>{{ rps.natureza_operacao }}</NaturezaOperacao>
<RegimeEspecialTributacao>{{ rps.regime_tributacao }}</RegimeEspecialTributacao>
<OptanteSimplesNacional>{{ rps.optante_simples }}</OptanteSimplesNacional>
<IncentivadorCultural>{{ rps.incentivador_cultural }}</IncentivadorCultural>
<Status>{{ rps.status }}</Status>
<RpsSubstituido>
<Numero>{{ rps.numero_substituido }}</Numero>
<Serie>{{ rps.serie_substituido }}</Serie>
<Tipo>{{ rps.tipo_substituido }}</Tipo>
</RpsSubstituido>
<Servico>
<Valores>
<ValorServicos>{{ rps.valor_servico }}</ValorServicos>
<ValorDeducoes>{{ rps.valor_deducao }}</ValorDeducoes>
<ValorPis>{{ rps.valor_pis }}</ValorPis>
<ValorCofins>{{ rps.valor_cofins }}</ValorCofins>
<ValorInss>{{ rps.valor_inss }}</ValorInss>
<ValorIr>{{ rps.valor_ir }}</ValorIr>
<ValorCsll>{{ rps.valor_csll }}</ValorCsll>
<IssRetido>{{ rps.iss_retido }}</IssRetido>
<ValorIss>{{ rps.valor_iss }}</ValorIss>
<ValorIssRetido>{{ rps.valor_iss_retido }}</ValorIssRetido>
<OutrasRetencoes>{{ rps.outras_retencoes }}</OutrasRetencoes>
<BaseCalculo>{{ rps.base_calculo }}</BaseCalculo>
<Aliquota>{{ rps.aliquota_issqn }}</Aliquota>
<ValorLiquidoNfse>{{ rps.valor_liquido_nfse }}</ValorLiquidoNfse>
<DescontoIncondicionado>{{ rps.desconto_incondicionado }}</DescontoIncondicionado>
<DescontoCondicionado>{{ rps.desconto_condicionado }}</DescontoCondicionado>
</Valores>
<ItemListaServico>{{ rps.codigo_servico }}</ItemListaServico>
<CodigoCnae>{{ rps.cnae_servico }}</CodigoCnae>
<CodigoTributacaoMunicipio>{{ rps.codigo_tributacao_municipio }}</CodigoTributacaoMunicipio>
<Discriminacao>{{ rps.descricao }}</Discriminacao>
<CodigoMunicipio>{{ rps.codigo_municipio }}</CodigoMunicipio>
</Servico>
<Prestador>
<Cnpj>{{ rps.prestador.cnpj }}</Cnpj>
<InscricaoMunicipal>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipal>
</Prestador>
<Tomador>
<IdentificacaoTomador>
<CpfCnpj>
{% if rps.tomador.cnpj_cpf|length == 14 %}
<Cnpj>{{ rps.tomador.cnpj_cpf }}</Cnpj>
{% endif %}
{% if rps.tomador.cnpj_cpf|length == 11 %}
<Cpf>{{ rps.tomador.cnpj_cpf }}</Cpf>
{% endif %}
</CpfCnpj>
<InscricaoMunicipal>{{ rps.tomador.inscricao_municipal }}</InscricaoMunicipal>
</IdentificacaoTomador>
<RazaoSocial>{{ rps.tomador.razao_social }}</RazaoSocial>
<Endereco>
<Endereco>{{ rps.tomador.logradouro }}</Endereco>
<Numero>{{ rps.tomador.numero }}</Numero>
<Complemento>{{ rps.tomador.complemento }}</Complemento>
<Bairro>{{ rps.tomador.bairro }}</Bairro>
<CodigoMunicipio>{{ rps.tomador.cidade }}</CodigoMunicipio>
<Uf>{{ rps.tomador.uf }}</Uf>
<Cep>{{ rps.tomador.cep }}</Cep>
</Endereco>
<Contato>
<Telefone>{{ rps.tomador.telefone }}</Telefone>
<Email>{{ rps.tomador.email }}</Email>
</Contato>
</Tomador>
{% if rps.intermediario is defined -%}
<IntermediarioServico>
<RazaoSocial>{{ rps.intermediario.razao_social }}</RazaoSocial>
<CpfCnpj>
<Cnpj>{{ rps.intermediario.cnpj }}</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>{{ rps.intermediario.inscricao_municipal }}</InscricaoMunicipal>
</IntermediarioServico>
{% endif %}
{% if rps.construcao_civil is defined -%}
<ContrucaoCivil>
<CodigoObra>{{ rps.construcao_civil.codigo_obra }}</CodigoObra>
<Art>{{ rps.construcao_civil.art }}</Art>
</ContrucaoCivil>
{% endif %}
</InfRps>
</Rps>

74
pytrustnfe/nfse/carioca/__init__.py

@ -0,0 +1,74 @@
# © 2018 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import suds
from pytrustnfe.client import get_authenticated_client
from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key
from pytrustnfe.xml import render_xml, sanitize_response
from pytrustnfe.nfe.assinatura import Assinatura
def _render(certificado, method, **kwargs):
path = os.path.join(os.path.dirname(__file__), 'templates')
xml_send = render_xml(path, '%s.xml' % method, True, **kwargs)
reference = ''
if method == 'GerarNfse':
reference = 'r%s' % kwargs['rps']['numero']
elif method == 'CancelarNfse':
reference = 'Cancelamento_NF%s' % kwargs['cancelamento']['numero_nfse']
signer = Assinatura(certificado.pfx, certificado.password)
xml_send = signer.assina_xml(xml_send, reference)
return xml_send.encode('utf-8')
def _send(certificado, method, **kwargs):
base_url = ''
if kwargs['ambiente'] == 'producao':
base_url = 'https://notacarioca.rio.gov.br/WSNacional/nfse.asmx?wsdl'
else:
base_url = 'https://homologacao.notacarioca.rio.gov.br/WSNacional/nfse.asmx?wsdl' # noqa
xml_send = kwargs["xml"].decode('utf-8')
cert, key = extract_cert_and_key_from_pfx(
certificado.pfx, certificado.password)
cert, key = save_cert_key(cert, key)
client = get_authenticated_client(base_url, cert, key)
try:
response = getattr(client.service, method)(xml_send)
except suds.WebFault as e:
return {
'sent_xml': str(xml_send),
'received_xml': str(e.fault.faultstring),
'object': None
}
response, obj = sanitize_response(response)
return {
'sent_xml': str(xml_send),
'received_xml': str(response),
'object': obj
}
def xml_gerar_nfse(certificado, **kwargs):
return _render(certificado, 'GerarNfse', **kwargs)
def gerar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_gerar_nfse(certificado, **kwargs)
return _send(certificado, 'GerarNfse', **kwargs)
def xml_cancelar_nfse(certificado, **kwargs):
return _render(certificado, 'CancelarNfse', **kwargs)
def cancelar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_cancelar_nfse(certificado, **kwargs)
return _send(certificado, 'CancelarNfse', **kwargs)

13
pytrustnfe/nfse/carioca/templates/CancelarNfse.xml

@ -0,0 +1,13 @@
<CancelarNfseEnvio xmlns="http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd">
<Pedido>
<InfPedidoCancelamento Id="Cancelamento_NF{{ cancelamento.numero_nfse }}">
<IdentificacaoNfse>
<Numero>{{ cancelamento.numero_nfse }}</Numero>
<Cnpj>{{ cancelamento.cnpj_prestador }}</Cnpj>
<InscricaoMunicipal>{{ cancelamento.inscricao_municipal }}</InscricaoMunicipal>
<CodigoMunicipio>{{ cancelamento.cidade }}</CodigoMunicipio>
</IdentificacaoNfse>
<CodigoCancelamento>1</CodigoCancelamento>
</InfPedidoCancelamento>
</Pedido>
</CancelarNfseEnvio>

3
pytrustnfe/nfse/carioca/templates/GerarNfse.xml

@ -0,0 +1,3 @@
<GerarNfseEnvio xmlns="http://notacarioca.rio.gov.br/WSNacional/XSD/1/nfse_pcrj_v01.xsd">
{% include 'Rps.xml' %}
</GerarNfseEnvio>

91
pytrustnfe/nfse/carioca/templates/Rps.xml

@ -0,0 +1,91 @@
<Rps>
<InfRps xmlns="http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd" Id="r{{ rps.numero }}">
<IdentificacaoRps>
<Numero>{{ rps.numero }}</Numero>
<Serie>{{ rps.serie }}</Serie>
<Tipo>{{ rps.tipo_rps }}</Tipo>
</IdentificacaoRps>
<DataEmissao>{{ rps.data_emissao }}</DataEmissao>
<NaturezaOperacao>{{ rps.natureza_operacao }}</NaturezaOperacao>
<RegimeEspecialTributacao>{{ rps.regime_tributacao }}</RegimeEspecialTributacao>
<OptanteSimplesNacional>{{ rps.optante_simples }}</OptanteSimplesNacional>
<IncentivadorCultural>{{ rps.incentivador_cultural }}</IncentivadorCultural>
<Status>{{ rps.status }}</Status>
<RpsSubstituido>
<Numero>{{ rps.numero_substituido }}</Numero>
<Serie>{{ rps.serie_substituido }}</Serie>
<Tipo>{{ rps.tipo_substituido }}</Tipo>
</RpsSubstituido>
<Servico>
<Valores>
<ValorServicos>{{ rps.valor_servico }}</ValorServicos>
<ValorDeducoes>{{ rps.valor_deducao }}</ValorDeducoes>
<ValorPis>{{ rps.valor_pis }}</ValorPis>
<ValorCofins>{{ rps.valor_cofins }}</ValorCofins>
<ValorInss>{{ rps.valor_inss }}</ValorInss>
<ValorIr>{{ rps.valor_ir }}</ValorIr>
<ValorCsll>{{ rps.valor_csll }}</ValorCsll>
<IssRetido>{{ rps.iss_retido }}</IssRetido>
<ValorIss>{{ rps.valor_iss }}</ValorIss>
<ValorIssRetido>{{ rps.valor_iss_retido }}</ValorIssRetido>
<OutrasRetencoes>{{ rps.outras_retencoes }}</OutrasRetencoes>
<BaseCalculo>{{ rps.base_calculo }}</BaseCalculo>
<Aliquota>{{ rps.aliquota_issqn }}</Aliquota>
<ValorLiquidoNfse>{{ rps.valor_liquido_nfse }}</ValorLiquidoNfse>
<DescontoIncondicionado>{{ rps.desconto_incondicionado }}</DescontoIncondicionado>
<DescontoCondicionado>{{ rps.desconto_condicionado }}</DescontoCondicionado>
</Valores>
<ItemListaServico>{{ rps.codigo_servico }}</ItemListaServico>
<CodigoCnae>{{ rps.cnae_servico }}</CodigoCnae>
<CodigoTributacaoMunicipio>{{ rps.codigo_tributacao_municipio }}</CodigoTributacaoMunicipio>
<Discriminacao>{{ rps.descricao }}</Discriminacao>
<CodigoMunicipio>{{ rps.codigo_municipio }}</CodigoMunicipio>
</Servico>
<Prestador>
<Cnpj>{{ rps.prestador.cnpj }}</Cnpj>
<InscricaoMunicipal>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipal>
</Prestador>
<Tomador>
<IdentificacaoTomador>
<CpfCnpj>
{% if rps.tomador.cnpj_cpf|length == 14 %}
<Cnpj>{{ rps.tomador.cnpj_cpf }}</Cnpj>
{% endif %}
{% if rps.tomador.cnpj_cpf|length == 11 %}
<Cpf>{{ rps.tomador.cnpj_cpf }}</Cpf>
{% endif %}
</CpfCnpj>
<InscricaoMunicipal>{{ rps.tomador.inscricao_municipal }}</InscricaoMunicipal>
</IdentificacaoTomador>
<RazaoSocial>{{ rps.tomador.razao_social }}</RazaoSocial>
<Endereco>
<Endereco>{{ rps.tomador.logradouro }}</Endereco>
<Numero>{{ rps.tomador.numero }}</Numero>
<Complemento>{{ rps.tomador.complemento }}</Complemento>
<Bairro>{{ rps.tomador.bairro }}</Bairro>
<CodigoMunicipio>{{ rps.tomador.cidade }}</CodigoMunicipio>
<Uf>{{ rps.tomador.uf }}</Uf>
<Cep>{{ rps.tomador.cep }}</Cep>
</Endereco>
<Contato>
<Telefone>{{ rps.tomador.telefone }}</Telefone>
<Email>{{ rps.tomador.email }}</Email>
</Contato>
</Tomador>
{% if rps.intermediario is defined -%}
<IntermediarioServico>
<RazaoSocial>{{ rps.intermediario.razao_social }}</RazaoSocial>
<CpfCnpj>
<Cnpj>{{ rps.intermediario.cnpj }}</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>{{ rps.intermediario.inscricao_municipal }}</InscricaoMunicipal>
</IntermediarioServico>
{% endif %}
{% if rps.construcao_civil is defined -%}
<ContrucaoCivil>
<CodigoObra>{{ rps.construcao_civil.codigo_obra }}</CodigoObra>
<Art>{{ rps.construcao_civil.art }}</Art>
</ContrucaoCivil>
{% endif %}
</InfRps>
</Rps>

131
pytrustnfe/nfse/dsf/__init__.py

@ -0,0 +1,131 @@
# -*- encoding: utf-8 -*-
# © 2017 Fábio Luna, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import suds
from lxml import etree
from pytrustnfe.xml import render_xml, sanitize_response
from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key
from pytrustnfe.nfse.assinatura import Assinatura
from pytrustnfe.client import get_client
def _render(certificado, method, **kwargs):
path = os.path.join(os.path.dirname(__file__), 'templates')
if method == "testeEnviar":
xml_send = render_xml(path, 'enviar.xml', True, **kwargs)
else:
xml_send = render_xml(path, '%s.xml' % method, False, **kwargs)
if type(xml_send) != str:
xml_send = etree.tostring(xml_send)
return xml_send
def _get_url(**kwargs):
try:
cod_cidade = kwargs['nfse']['cidade']
except (KeyError, TypeError):
raise KeyError("Código de cidade inválido!")
urls = {
# Belém - PA
'2715': 'http://www.issdigitalbel.com.br/WsNFe2/LoteRps.jws',
# Sorocaba - SP
'7145': 'http://issdigital.sorocaba.sp.gov.br/WsNFe2/LoteRps.jws',
# Teresina - PI
'1219': 'http://www.issdigitalthe.com.br/WsNFe2/LoteRps.jws',
# Campinas - SP
'6291': 'http://issdigital.campinas.sp.gov.br/WsNFe2/LoteRps.jws?wsdl',
# Uberlandia - MG
'5403': 'http://udigital.uberlandia.mg.gov.br/WsNFe2/LoteRps.jws',
# São Luis - MA
'0921': 'https://stm.semfaz.saoluis.ma.gov.br/WsNFe2/LoteRps?wsdl',
# Campo Grande - MS
'2729': 'http://issdigital.pmcg.ms.gov.br/WsNFe2/LoteRps.jws?wsdl',
}
try:
return urls[str(cod_cidade)]
except KeyError:
raise KeyError("DSF não emite notas da cidade {}!".format(
cod_cidade))
def _send(certificado, method, **kwargs):
url = _get_url(**kwargs)
path = os.path.join(os.path.dirname(__file__), 'templates')
xml_send = _render(path, method, **kwargs)
client = get_client(url)
response = False
if certificado:
cert, key = extract_cert_and_key_from_pfx(
certificado.pfx, certificado.password)
cert, key = save_cert_key(cert, key)
signer = Assinatura(cert, key, certificado.password)
xml_send = signer.assina_xml(xml_send, '')
try:
response = getattr(client.service, method)(xml_send)
response, obj = sanitize_response(response.encode())
except suds.WebFault as e:
return {
'sent_xml': xml_send,
'received_xml': e.fault.faultstring,
'object': None
}
except Exception as e:
if response:
raise Exception(response)
else:
raise e
return {
'sent_xml': xml_send,
'received_xml': response,
'object': obj
}
def xml_enviar(certificado, **kwargs):
return _render(certificado, 'enviar', **kwargs)
def enviar(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_enviar(certificado, **kwargs)
return _send(certificado, 'enviar', **kwargs)
def xml_teste_enviar(certificado, **kwargs):
return _render(certificado, 'testeEnviar', **kwargs)
def teste_enviar(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_teste_enviar(certificado, **kwargs)
return _send(certificado, 'testeEnviar', **kwargs)
def cancelar(certificado, ** kwargs):
return _send(certificado, 'cancelar', **kwargs)
def consulta_lote(**kwargs):
return _send(False, 'consultarLote', **kwargs)
def xml_consultar_nfse_rps(certificado, **kwargs):
return _render(certificado, 'consultarNFSeRps', **kwargs)
def consultar_nfse_rps(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_consultar_nfse_rps(certificado, **kwargs)
return _send(certificado, 'consultarNFSeRps', **kwargs)

18
pytrustnfe/nfse/dsf/templates/cancelar.xml

@ -0,0 +1,18 @@
<ns1:ReqCancelamentoNFSe xmlns:ns1="http://localhost:8080/WsNFe2/lote"
xmlns:tipos="http://localhost:8080/WsNFe2/tp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://localhost:8080/WsNFe2/lote http://localhost:8080/WsNFe2/xsd/ReqCancelamentoNFSe.xsd">
<Cabecalho>
<CodCidade>{{ cancelamento.cidade }}</CodCidade>
<CPFCNPJRemetente>{{ cancelamento.cpf_cnpj }}</CPFCNPJRemetente>
<transacao>true</transacao>
<Versao>1</Versao>
</Cabecalho>
<Lote Id="lote:1ABCDZ">
<Nota Id="nota:{{ cancelamento.nota_id }}">
<InscricaoMunicipalPrestador>{{ cancelamento.inscricao_municipal }}</InscricaoMunicipalPrestador>
<NumeroNota>{{ cancelamento.nota_id }}</NumeroNota>
<CodigoVerificacao>{{ cancelamento.assinatura }}</CodigoVerificacao>
<MotivoCancelamento>{{ cancelamento.motivo }}</MotivoCancelamento>
</Nota>
</Lote>
</ns1:ReqCancelamentoNFSe>

11
pytrustnfe/nfse/dsf/templates/consulta_notas.xml

@ -0,0 +1,11 @@
<ns1:ReqConsultaNotas xmlns:ns1="http://localhost:8080/WsNFe2/lote" xmlns:tipos="http://localhost:8080/WsNFe2/tp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://localhost:8080/WsNFe2/lote http://localhost:8080/WsNFe2/xsd/ReqConsultaNotas.xsd">
<Cabecalho Id="Consulta:notas">
<CodCidade>{{ consulta.cidade }}</CodCidade>
<CPFCNPJRemetente>{{ consulta.cpf_cnpj }}</CPFCNPJRemetente>
<InscricaoMunicipalPrestador>{{ consulta.inscricao_municipal }}</InscricaoMunicipalPrestador>
<dtInicio>{{ consulta.data_inicio }}</dtInicio>
<dtFim>{{ consulta.data_final }}</dtFim>
<NotaInicial>{{ consulta.nota_inicial }}</NotaInicial>
<Versao>1</Versao>
</Cabecalho>
</ns1:ReqConsultaNotas>

10
pytrustnfe/nfse/dsf/templates/consultarLote.xml

@ -0,0 +1,10 @@
<ns1:ReqConsultaLote xmlns:ns1="http://localhost:8080/WsNFe2/lote"
xmlns:tipos="http://localhost:8080/WsNFe2/tp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://localhost:8080/WsNFe2/lote http://localhost:8080/WsNFe2/xsd/ReqConsultaLote.xsd">
<Cabecalho>
<CodCidade>{{ consulta.cidade }}</CodCidade>
<CPFCNPJRemetente>{{ consulta.cpf_cnpj }}</CPFCNPJRemetente>
<Versao>1</Versao>
<NumeroLote>{{ consulta.lote }}</NumeroLote>
</Cabecalho>
</ns1:ReqConsultaLote>

22
pytrustnfe/nfse/dsf/templates/consultarNFSeRps.xml

@ -0,0 +1,22 @@
<ns1:ReqConsultaNFSeRPS
xmlns:ns1="http://localhost:8080/WsNFe2/lote"
xmlns:tipos="http://localhost:8080/WsNFe2/tp"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://localhost:8080/WsNFe2/lote http://localhost:8080/WsNFe2/xsd/ReqConsultaNFSeRPS.xsd">
<Cabecalho>
<CodCidade>{{ nfse.cidade }}</CodCidade>
<CPFCNPJRemetente>{{ nfse.cpf_cnpj }}</CPFCNPJRemetente>
<transacao>true</transacao>
<Versao>1</Versao>
</Cabecalho>
<Lote Id="lote:{{ nfse.lote }}">
{% for rps in nfse.lista_rps -%}
<RPSConsulta>
<RPS Id="rps:{{ rps.numero }}">
<InscricaoMunicipalPrestador>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipalPrestador>
<NumeroRPS>{{ rps.numero }}</NumeroRPS>
<SeriePrestacao>{{ rps.serie_prestacao }}</SeriePrestacao>
</RPS>
</RPSConsulta>
{% endfor %}
</Lote>
</ns1:ReqConsultaNFSeRPS>

108
pytrustnfe/nfse/dsf/templates/enviar.xml

@ -0,0 +1,108 @@
<ns1:ReqEnvioLoteRPS xmlns:ns1="http://localhost:8080/WsNFe2/lote"
xmlns:tipos="http://localhost:8080/WsNFe2/tp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://localhost:8080/WsNFe2/lote http://localhost:8080/WsNFe2/xsd/ReqEnvioLoteRPS.xsd">
<Cabecalho>
<CodCidade>{{ nfse.cidade }}</CodCidade>
<CPFCNPJRemetente>{{ nfse.cpf_cnpj }}</CPFCNPJRemetente>
<RazaoSocialRemetente>{{ nfse.remetente }}</RazaoSocialRemetente>
<transacao>{{ nfse.transacao }}</transacao>
<dtInicio>{{ nfse.data_inicio|format_date }}</dtInicio>
<dtFim>{{ nfse.data_fim|format_date }}</dtFim>
<QtdRPS>{{ nfse.total_rps }}</QtdRPS>
<ValorTotalServicos>{{ nfse.total_servicos }}</ValorTotalServicos>
<ValorTotalDeducoes>{{ nfse.total_deducoes }}</ValorTotalDeducoes>
<Versao>1</Versao>
<MetodoEnvio>WS</MetodoEnvio>
</Cabecalho>
<Lote Id="{{ nfse.lote_id }}">
{% for rps in nfse.lista_rps -%}
<RPS Id="{{ rps.numero }}">
<Assinatura>{{ rps.assinatura }}</Assinatura>
<InscricaoMunicipalPrestador>{{ rps.prestador.inscricao_municipal }}
</InscricaoMunicipalPrestador>
<RazaoSocialPrestador>{{ rps.prestador.razao_social }}</RazaoSocialPrestador>
<TipoRPS>RPS</TipoRPS>
<SerieRPS>{{ rps.serie }}</SerieRPS>
<NumeroRPS>{{ rps.numero }}</NumeroRPS>
<DataEmissaoRPS>{{ rps.data_emissao|format_datetime }}
</DataEmissaoRPS>
<SituacaoRPS>{{ rps.situacao }}</SituacaoRPS>
<SerieRPSSubstituido></SerieRPSSubstituido>
<NumeroRPSSubstituido>0</NumeroRPSSubstituido>
<NumeroNFSeSubstituida>0</NumeroNFSeSubstituida>
<DataEmissaoNFSeSubstituida>1900-01-01</DataEmissaoNFSeSubstituida>
<SeriePrestacao>{{ rps.serie_prestacao }}</SeriePrestacao>
<InscricaoMunicipalTomador>{{ rps.tomador.inscricao_municipal }}</InscricaoMunicipalTomador>
<CPFCNPJTomador>{{ rps.tomador.cpf_cnpj }}</CPFCNPJTomador>
<RazaoSocialTomador>{{ rps.tomador.razao_social }}
</RazaoSocialTomador>
<TipoLogradouroTomador>{{ rps.tomador.tipo_logradouro }}
</TipoLogradouroTomador>
<LogradouroTomador>{{ rps.tomador.logradouro }}</LogradouroTomador>
<NumeroEnderecoTomador>{{ rps.tomador.numero }}
</NumeroEnderecoTomador>
<TipoBairroTomador>{{ rps.tomador.tipo_bairro }}</TipoBairroTomador>
<BairroTomador>{{ rps.tomador.bairro }}</BairroTomador>
<CidadeTomador>{{ rps.tomador.cidade }}</CidadeTomador>
<CidadeTomadorDescricao>{{ rps.tomador.cidade_descricao }}
</CidadeTomadorDescricao>
<CEPTomador>{{ rps.tomador.cep }}</CEPTomador>
<EmailTomador>{{ rps.tomador.email }}</EmailTomador>
<CodigoAtividade>{{ rps.codigo_atividade }}</CodigoAtividade>
<AliquotaAtividade>{{ rps.aliquota_atividade }}</AliquotaAtividade>
<TipoRecolhimento>{{ rps.tipo_recolhimento }}</TipoRecolhimento>
<MunicipioPrestacao>{{ rps.municipio_prestacao }}
</MunicipioPrestacao>
<MunicipioPrestacaoDescricao>{{ rps.municipio_descricao_prestacao }}
</MunicipioPrestacaoDescricao>
<Operacao>{{ rps.operacao }}</Operacao>
<Tributacao>{{ rps.tributacao }}</Tributacao>
<ValorPIS>{{ rps.valor_pis }}</ValorPIS>
<ValorCOFINS>{{ rps.valor_cofins }}</ValorCOFINS>
<ValorINSS>{{ rps.valor_inss }}</ValorINSS>
<ValorIR>{{ rps.valor_ir }}</ValorIR>
<ValorCSLL>{{ rps.valor_csll }}</ValorCSLL>
<AliquotaPIS>{{ rps.aliquota_pis }}</AliquotaPIS>
<AliquotaCOFINS>{{ rps.aliquota_cofins }}</AliquotaCOFINS>
<AliquotaINSS>{{ rps.aliquota_inss }}</AliquotaINSS>
<AliquotaIR>{{ rps.aliquota_ir }}</AliquotaIR>
<AliquotaCSLL>{{ rps.aliquota_csll }}</AliquotaCSLL>
<DescricaoRPS>{{ rps.descricao }}</DescricaoRPS>
<DDDPrestador>{{ rps.prestador.ddd }}</DDDPrestador>
<TelefonePrestador>{{ rps.prestador.telefone }}</TelefonePrestador>
<DDDTomador>{{ rps.tomador.ddd }}</DDDTomador>
<TelefoneTomador>{{ rps.tomador.telefone }}</TelefoneTomador>
<MotCancelamento>{{ rps.motivo_cancelamento }}</MotCancelamento>
{% if rps.deducoes|count > 0 %}
<Deducoes>
{% for deducao in rps.deducoes -%}
<Deducao>
<DeducaoPor>{{ deducao.por }}</DeducaoPor>
<TipoDeducao>{{ deducao.tipo }}</TipoDeducao>
<CPFCNPJReferencia>{{ deducao.cnpj_referencia }}</CPFCNPJReferencia>
<NumeroNFReferencia>{{ deducao.nf_referencia }}</NumeroNFReferencia>
<ValorTotalReferencia>{{ deducao.valor_referencia }}</ValorTotalReferencia>
<PercentualDeduzir>{{ deducao.percentual_deduzir }}</PercentualDeduzir>
<ValorDeduzir>{{ deducao.valor_deduzir }}</ValorDeduzir>
</Deducao>
{% endfor %}
</Deducoes>
{% endif %}
{% if rps.deducoes|count == 0 %}
<Deducoes />
{% endif %}
<Itens>
{% for item in rps.itens -%}
<Item>
<DiscriminacaoServico>{{ item.descricao }}</DiscriminacaoServico>
<Quantidade>{{ item.quantidade }}</Quantidade>
<ValorUnitario>{{ item.valor_unitario }}</ValorUnitario>
<ValorTotal>{{ item.valor_total }}</ValorTotal>
<Tributavel>S</Tributavel>
</Item>
{% endfor %}
</Itens>
</RPS>
{% endfor %}
</Lote>
</ns1:ReqEnvioLoteRPS>

12
pytrustnfe/nfse/dsf/templates/soap_header.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:dsf="http://dsfnet.com.br">
<soapenv:Body>
<dsf:enviar soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<mensagemXml xsi:type="xsd:string"><![CDATA[
{% block content %}{% endblock %}
]]></mensagemXml>
</dsf:enviar>
</soapenv:Body>
</soapenv:Envelope>

119
pytrustnfe/nfse/floripa/__init__.py

@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
# © 2017 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import hashlib
import base64
import requests
from pytrustnfe.xml import render_xml, sanitize_response
from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key
from pytrustnfe.nfse.assinatura import Assinatura
URLS = {
'producao': {
'processar_nota': 'https://nfps-e.pmf.sc.gov.br/api/v1/processamento/notas/processa',
'cancelar_nota': 'https://nfps-e.pmf.sc.gov.br/api/v1/cancelamento/notas/cancela'
},
'homologacao': {
'processar_nota': 'https://nfps-e-hml.pmf.sc.gov.br/api/v1/processamento/notas/processa',
'cancelar_nota': 'https://nfps-e-hml.pmf.sc.gov.br/api/v1/cancelamento/notas/cancela'
}
}
def _render(certificado, method, **kwargs):
path = os.path.join(os.path.dirname(__file__), 'templates')
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)
signer = Assinatura(cert, key, certificado.password)
xml_send = signer.assina_xml(xml_send, '')
return xml_send
def _get_oauth_token(**kwargs):
if kwargs['ambiente'] == 'producao':
url = 'https://nfps-e.pmf.sc.gov.br/api/v1/autenticacao/oauth/token'
else:
url = 'https://nfps-e-hml.pmf.sc.gov.br/api/v1/autenticacao/oauth/token'
m = hashlib.md5()
secret = "%s:%s" % (kwargs["client_id"], kwargs["secret_id"])
auth = base64.b64encode(secret.encode('utf-8'))
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Basic %s" % auth.decode('utf-8').replace('\n', '')
}
m.update(kwargs["password"].encode('utf-8'))
password = m.hexdigest().upper()
dados = "grant_type=password&username=%s&password=%s&client_id=%s&client_secret=%s" % (
kwargs["username"], password, kwargs["client_id"], kwargs["secret_id"])
r = requests.post(url, data=dados, headers=headers)
if r.status_code == 200:
return r.json()
else:
return r.json()
def _send(certificado, method, **kwargs):
url = URLS[kwargs['ambiente']][method]
xml_send = kwargs['xml']
token = _get_oauth_token(**kwargs)
if "access_token" not in token:
raise Exception("%s - %s: %s" % (token["status"], token["error"],
token["message"]))
kwargs.update({"numero": 1, 'access_token': token["access_token"]})
headers = {"Accept": "application/xml;charset=UTF-8",
"Content-Type": "application/xml",
"Authorization": "Bearer %s" % kwargs['access_token']}
r = requests.post(url, headers=headers, data=xml_send)
response, obj = sanitize_response(r.text.strip().encode('utf-8'))
return {
'sent_xml': xml_send,
'received_xml': response,
'object': obj,
'status_code': r.status_code,
}
def xml_processar_nota(certificado, **kwargs):
return _render(certificado, 'processar_nota', **kwargs)
def processar_nota(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_processar_nota(certificado, **kwargs)
return _send(certificado, 'processar_nota', **kwargs)
def xml_cancelar_nota(certificado, **kwargs):
return _render(certificado, 'cancelar_nota', **kwargs)
def cancelar_nota(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_cancelar_nota(certificado, **kwargs)
return _send(certificado, 'cancelar_nota', **kwargs)
def consultar_nota(certificado, **kwargs):
if kwargs['ambiente'] == 'producao':
url = "https://nfps-e.pmf.sc.gov.br/api/v1/consultas/notas/numero/%s" % (kwargs["numero"])
else:
url = "https://nfps-e-hml.pmf.sc.gov.br/api/v1/consultas/notas/numero/%s" % (kwargs["numero"])
headers = {"Accept": "application/json",
"Authorization": "Bearer %s" % kwargs['access_token']}
r = requests.get(url, headers=headers)
print(r.status_code)
if r.status_code == 200:
return r.text
else:
return r.text

7
pytrustnfe/nfse/floripa/templates/cancelar_nota.xml

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<xmlCancelamentoNfpse>
<motivoCancelamento>{{ cancelamento.motivo }}</motivoCancelamento>
<nuAedf>{{ cancelamento.aedf }}</nuAedf>
<nuNotaFiscal>{{ cancelamento.numero }}</nuNotaFiscal>
<codigoVerificacao>{{ cancelamento.codigo_verificacao }}</codigoVerificacao>
</xmlCancelamentoNfpse>

41
pytrustnfe/nfse/floripa/templates/processar_nota.xml

@ -0,0 +1,41 @@
<?xml version="1.0"?>
<xmlProcessamentoNfpse>
<bairroTomador>{{ rps.tomador.bairro|normalize|escape }}</bairroTomador>
<baseCalculo>{{ rps.base_calculo }}</baseCalculo>
<baseCalculoSubstituicao>0.0</baseCalculoSubstituicao>
<cfps>{{ rps.cfps }}</cfps>
<codigoMunicipioTomador>{{ rps.tomador.cidade }}</codigoMunicipioTomador>
<codigoPostalTomador>{{ rps.tomador.cep }}</codigoPostalTomador>
<complementoEnderecoTomador>{{ rps.tomador.complemento|normalize|escape }}</complementoEnderecoTomador>
<dadosAdicionais>{{ rps.observacoes|normalize|escape }}</dadosAdicionais>
<dataEmissao>{{ rps.data_emissao }}</dataEmissao>
<emailTomador>{{ rps.tomador.email }}</emailTomador>
<identificacao>{{ rps.numero }}</identificacao>
<identificacaoTomador>{{ rps.tomador.cnpj_cpf }}</identificacaoTomador>
<inscricaoMunicipalTomador>{{ rps.tomador.inscricao_municipal }}</inscricaoMunicipalTomador>
<itensServico>
{% for item in rps.itens_servico -%}
<itemServico>
<aliquota>{{ item.aliquota }}</aliquota>
<baseCalculo>{{ item.base_calculo }}</baseCalculo>
<cst>{{ item.cst_servico }}</cst>
<descricaoServico>{{ item.name|normalize|escape }}</descricaoServico>
<idCNAE>{{ item.cnae }}</idCNAE>
<quantidade>{{ item.quantidade }}</quantidade>
<valorTotal>{{ item.valor_total }}</valorTotal>
<valorUnitario>{{ item.valor_unitario }}</valorUnitario>
</itemServico>
{% endfor %}
</itensServico>
<logradouroTomador>{{ rps.tomador.logradouro|normalize|escape }}</logradouroTomador>
<nomeMunicipioTomador></nomeMunicipioTomador>
<numeroAEDF>{{ rps.aedf }}</numeroAEDF>
<numeroEnderecoTomador>{{ rps.tomador.numero }}</numeroEnderecoTomador>
<paisTomador>1058</paisTomador>
<razaoSocialTomador>{{ rps.tomador.razao_social|normalize|escape }}</razaoSocialTomador>
<telefoneTomador>{{ rps.tomador.telefone }}</telefoneTomador>
<ufTomador>{{ rps.tomador.uf }}</ufTomador>
<valorISSQN>{{rps.valor_issqn }}</valorISSQN>
<valorISSQNSubstituicao>0.0</valorISSQNSubstituicao>
<valorTotalServicos>{{ rps.valor_total }}</valorTotalServicos>
</xmlProcessamentoNfpse>

35
pytrustnfe/nfse/ginfes/__init__.py

@ -3,9 +3,12 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import suds
from requests import Session
from zeep import Client
from zeep.transports import Transport
from requests.packages.urllib3 import disable_warnings
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.nfe.assinatura import Assinatura
@ -33,19 +36,21 @@ def _send(certificado, method, **kwargs):
cert, key = extract_cert_and_key_from_pfx(
certificado.pfx, certificado.password)
cert, key = save_cert_key(cert, key)
client = get_authenticated_client(base_url, cert, key)
try:
xml_send = kwargs['xml']
header = '<ns2:cabecalho xmlns:ns2="http://www.ginfes.com.br/cabecalho_v03.xsd" versao="3"><versaoDados>3</versaoDados></ns2:cabecalho>' #noqa
response = getattr(client.service, method)(header, xml_send)
except suds.WebFault, e:
return {
'sent_xml': xml_send,
'received_xml': e.fault.faultstring,
'object': None
}
response, obj = sanitize_response(response)
header = '<ns2:cabecalho xmlns:ns2="http://www.ginfes.com.br/cabecalho_v03.xsd" versao="3"><versaoDados>3</versaoDados></ns2:cabecalho>' #noqa
disable_warnings()
session = Session()
session.cert = (cert, key)
session.verify = False
transport = Transport(session=session)
client = Client(base_url, transport=transport)
xml_send = kwargs['xml']
response = client.service[method](header, xml_send)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': xml_send,
'received_xml': response,

11
pytrustnfe/nfse/imperial/__init__.py

@ -20,18 +20,15 @@ def _send(certificado, method, **kwargs):
base_url = 'https://nfe.etransparencia.com.br/rj.petropolis/webservice/aws_nfe.aspx' # noqa
else:
base_url = 'https://nfehomologacao.etransparencia.com.br/rj.petropolis/webservice/aws_nfe.aspx' # noqa
xml_send = kwargs["xml"]
path = os.path.join(os.path.dirname(__file__), 'templates')
soap = render_xml(path, 'SoapRequest.xml', False, soap_body=xml_send)
soap = render_xml(path, 'SoapRequest.xml', False, soap_body=xml_send.decode())
client = HttpClient(base_url)
response = client.post_soap(soap, 'NFeaction/AWS_NFE.%s' % method)
response, obj = sanitize_response(response)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': xml_send,
'received_xml': response,
'sent_xml': xml_send.decode(),
'received_xml': response.decode(),
'object': obj
}

82
pytrustnfe/nfse/mga/__init__.py

@ -0,0 +1,82 @@
# © 2018 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
from lxml import etree
from requests import Session
from zeep import Client
from zeep.transports import Transport
from pytrustnfe.certificado import extract_cert_and_key_from_pfx, save_cert_key
from pytrustnfe.xml import render_xml, sanitize_response
from pytrustnfe.nfse.mga.assinatura import Assinatura
def _render(certificado, method, **kwargs):
path = os.path.join(os.path.dirname(__file__), 'templates')
xml_send = render_xml(path, '%s.xml' % method, True, **kwargs)
reference = ''
if method == 'GerarNfse':
reference = 'rps:%s' % kwargs['rps']['numero']
ref_lote = 'lote%s' % kwargs['rps']['numero_lote']
elif method == 'CancelarNfse':
reference = 'Cancelamento_NF%s' % kwargs['cancelamento']['numero_nfse']
signer = Assinatura(certificado.pfx, certificado.password)
xml_send = signer.assina_xml(xml_send, reference)
xml_send = signer.assina_xml(etree.fromstring(xml_send), ref_lote)
return xml_send.encode('utf-8')
def _send(certificado, method, **kwargs):
base_url = ''
if kwargs['ambiente'] == 'producao':
base_url = 'https://isse.maringa.gov.br/ws/?wsdl'
else:
base_url = 'https://isseteste.maringa.gov.br/ws/?wsdl'
xml_send = kwargs["xml"].decode('utf-8')
xml_cabecalho = '<?xml version="1.0" encoding="UTF-8"?>\
<cabecalho xmlns="http://www.abrasf.org.br/nfse.xsd" versao="1.00">\
<versaoDados>1.00</versaoDados></cabecalho>'
cert, key = extract_cert_and_key_from_pfx(
certificado.pfx, certificado.password)
cert, key = save_cert_key(cert, key)
session = Session()
session.cert = (cert, key)
session.verify = False
transport = Transport(session=session)
client = Client(base_url, transport=transport)
response = client.service[method](xml_cabecalho, xml_send)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': str(xml_send),
'received_xml': str(response),
'object': obj
}
def xml_gerar_nfse(certificado, **kwargs):
return _render(certificado, 'GerarNfse', **kwargs)
def gerar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_gerar_nfse(certificado, **kwargs)
return _send(certificado, 'GerarNfse', **kwargs)
def xml_cancelar_nfse(certificado, **kwargs):
return _render(certificado, 'CancelarNfse', **kwargs)
def cancelar_nfse(certificado, **kwargs):
if "xml" not in kwargs:
kwargs['xml'] = xml_cancelar_nfse(certificado, **kwargs)
return _send(certificado, 'CancelarNfse', **kwargs)

44
pytrustnfe/nfse/mga/assinatura.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# © 2016 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import signxml
from lxml import etree
from pytrustnfe.certificado import extract_cert_and_key_from_pfx
from signxml import XMLSigner
class Assinatura(object):
def __init__(self, arquivo, senha):
self.arquivo = arquivo
self.senha = senha
def assina_xml(self, xml_element, reference):
cert, key = extract_cert_and_key_from_pfx(self.arquivo, self.senha)
for element in xml_element.iter("*"):
if element.text is not None and not element.text.strip():
element.text = None
signer = XMLSigner(
method=signxml.methods.enveloped, signature_algorithm="rsa-sha1",
digest_algorithm='sha1',
c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315')
ns = {}
ns[None] = signer.namespaces['ds']
signer.namespaces = ns
ref_uri = ('#%s' % reference) if reference else None
signed_root = signer.sign(
xml_element, key=key.encode(), cert=cert.encode(),
reference_uri=ref_uri)
if reference:
element_signed = signed_root.find(".//*[@Id='%s']" % reference)
signature = signed_root.find(".//*[@URI='#%s']" % reference).getparent().getparent()
if element_signed is not None and signature is not None:
parent = element_signed.getparent()
parent.append(signature)
return etree.tostring(signed_root, encoding=str)

13
pytrustnfe/nfse/mga/templates/CancelarNfse.xml

@ -0,0 +1,13 @@
<CancelarNfseEnvio xmlns="http://www.abrasf.org.br/ABRASF/arquivos/nfse.xsd">
<Pedido xmlns="http://www.abrasf.org.br/nfse.xsd">
<InfPedidoCancelamento Id="pedidoCancelamento_{{ cancelamento.numero_nfse }}">
<IdentificacaoNfse>
<Numero>{{ cancelamento.numero_nfse }}</Numero>
<Cnpj>{{ cancelamento.cnpj_prestador }}</Cnpj>
<InscricaoMunicipal>{{ cancelamento.inscricao_municipal }}</InscricaoMunicipal>
<CodigoMunicipio>{{ cancelamento.cidade }}</CodigoMunicipio>
</IdentificacaoNfse>
<CodigoCancelamento>1</CodigoCancelamento>
</InfPedidoCancelamento>
</Pedido>
</CancelarNfseEnvio>

11
pytrustnfe/nfse/mga/templates/GerarNfse.xml

@ -0,0 +1,11 @@
<GerarNfseEnvio xmlns="http://www.abrasf.org.br/nfse.xsd">
<LoteRps Id="lote{{ rps.numero_lote }}" versao="1.00">
<NumeroLote>{{ rps.numero_lote }}</NumeroLote>
<Cnpj>{{ rps.prestador.cnpj }}</Cnpj>
<InscricaoMunicipal>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipal>
<QuantidadeRps>1</QuantidadeRps>
<ListaRps xmlns="http://www.abrasf.org.br/nfse.xsd">
{% include 'Rps.xml' %}
</ListaRps>
</LoteRps>
</GerarNfseEnvio>

91
pytrustnfe/nfse/mga/templates/Rps.xml

@ -0,0 +1,91 @@
<Rps>
<InfRps xmlns="http://www.abrasf.org.br/nfse.xsd" Id="rps:{{ rps.numero }}">
<IdentificacaoRps>
<Numero>{{ rps.numero }}</Numero>
<Serie>{{ rps.serie }}</Serie>
<Tipo>{{ rps.tipo_rps }}</Tipo>
</IdentificacaoRps>
<DataEmissao>{{ rps.data_emissao }}</DataEmissao>
<NaturezaOperacao>{{ rps.natureza_operacao }}</NaturezaOperacao>
<RegimeEspecialTributacao>{{ rps.regime_tributacao }}</RegimeEspecialTributacao>
<OptanteSimplesNacional>{{ rps.optante_simples }}</OptanteSimplesNacional>
<IncentivadorCultural>{{ rps.incentivador_cultural }}</IncentivadorCultural>
<Status>{{ rps.status }}</Status>
<RpsSubstituido>
<Numero>{{ rps.numero_substituido }}</Numero>
<Serie>{{ rps.serie_substituido }}</Serie>
<Tipo>{{ rps.tipo_substituido }}</Tipo>
</RpsSubstituido>
<Servico>
<Valores>
<ValorServicos>{{ rps.valor_servico }}</ValorServicos>
<ValorDeducoes>{{ rps.valor_deducao }}</ValorDeducoes>
<ValorPis>{{ rps.valor_pis }}</ValorPis>
<ValorCofins>{{ rps.valor_cofins }}</ValorCofins>
<ValorInss>{{ rps.valor_inss }}</ValorInss>
<ValorIr>{{ rps.valor_ir }}</ValorIr>
<ValorCsll>{{ rps.valor_csll }}</ValorCsll>
<IssRetido>{{ rps.iss_retido }}</IssRetido>
<ValorIss>{{ rps.valor_iss }}</ValorIss>
<ValorIssRetido>{{ rps.valor_iss_retido }}</ValorIssRetido>
<OutrasRetencoes>{{ rps.outras_retencoes }}</OutrasRetencoes>
<BaseCalculo>{{ rps.base_calculo }}</BaseCalculo>
<Aliquota>{{ rps.aliquota_issqn }}</Aliquota>
<ValorLiquidoNfse>{{ rps.valor_liquido_nfse }}</ValorLiquidoNfse>
<DescontoIncondicionado>{{ rps.desconto_incondicionado }}</DescontoIncondicionado>
<DescontoCondicionado>{{ rps.desconto_condicionado }}</DescontoCondicionado>
</Valores>
<ItemListaServico>{{ rps.codigo_servico }}</ItemListaServico>
<CodigoCnae>{{ rps.cnae_servico }}</CodigoCnae>
<CodigoTributacaoMunicipio>{{ rps.codigo_tributacao_municipio }}</CodigoTributacaoMunicipio>
<Discriminacao>{{ rps.descricao }}</Discriminacao>
<CodigoMunicipio>{{ rps.codigo_municipio }}</CodigoMunicipio>
</Servico>
<Prestador>
<Cnpj>{{ rps.prestador.cnpj }}</Cnpj>
<InscricaoMunicipal>{{ rps.prestador.inscricao_municipal }}</InscricaoMunicipal>
</Prestador>
<Tomador>
<IdentificacaoTomador>
<CpfCnpj>
{% if rps.tomador.cnpj_cpf|length == 14 %}
<Cnpj>{{ rps.tomador.cnpj_cpf }}</Cnpj>
{% endif %}
{% if rps.tomador.cnpj_cpf|length == 11 %}
<Cpf>{{ rps.tomador.cnpj_cpf }}</Cpf>
{% endif %}
</CpfCnpj>
<InscricaoMunicipal>{{ rps.tomador.inscricao_municipal }}</InscricaoMunicipal>
</IdentificacaoTomador>
<RazaoSocial>{{ rps.tomador.razao_social }}</RazaoSocial>
<Endereco>
<Endereco>{{ rps.tomador.logradouro }}</Endereco>
<Numero>{{ rps.tomador.numero }}</Numero>
<Complemento>{{ rps.tomador.complemento }}</Complemento>
<Bairro>{{ rps.tomador.bairro }}</Bairro>
<CodigoMunicipio>{{ rps.tomador.cidade }}</CodigoMunicipio>
<Uf>{{ rps.tomador.uf }}</Uf>
<Cep>{{ rps.tomador.cep }}</Cep>
</Endereco>
<Contato>
<Telefone>{{ rps.tomador.telefone }}</Telefone>
<Email>{{ rps.tomador.email }}</Email>
</Contato>
</Tomador>
{% if rps.intermediario is defined -%}
<IntermediarioServico>
<RazaoSocial>{{ rps.intermediario.razao_social }}</RazaoSocial>
<CpfCnpj>
<Cnpj>{{ rps.intermediario.cnpj }}</Cnpj>
</CpfCnpj>
<InscricaoMunicipal>{{ rps.intermediario.inscricao_municipal }}</InscricaoMunicipal>
</IntermediarioServico>
{% endif %}
{% if rps.construcao_civil is defined -%}
<ContrucaoCivil>
<CodigoObra>{{ rps.construcao_civil.codigo_obra }}</CodigoObra>
<Art>{{ rps.construcao_civil.art }}</Art>
</ContrucaoCivil>
{% endif %}
</InfRps>
</Rps>

13
pytrustnfe/nfse/paulistana/__init__.py

@ -18,10 +18,10 @@ def sign_tag(certificado, **kwargs):
if 'nfse' in kwargs:
for item in kwargs['nfse']['lista_rps']:
signed = crypto.sign(key, item['assinatura'], 'SHA1')
item['assinatura'] = b64encode(signed)
item['assinatura'] = b64encode(signed).decode()
if 'cancelamento' in kwargs:
signed = crypto.sign(key, kwargs['cancelamento']['assinatura'], 'SHA1')
kwargs['cancelamento']['assinatura'] = b64encode(signed)
kwargs['cancelamento']['assinatura'] = b64encode(signed).decode()
def _send(certificado, method, **kwargs):
@ -42,23 +42,22 @@ def _send(certificado, method, **kwargs):
cert, key = save_cert_key(cert, key)
client = get_authenticated_client(base_url, cert, key)
pfx_path = certificado.save_pfx()
signer = Assinatura(pfx_path, certificado.password)
signer = Assinatura(cert, key, certificado.password)
xml_send = signer.assina_xml(xml_send, '')
try:
response = getattr(client.service, method)(1, xml_send)
except suds.WebFault, e:
except suds.WebFault as e:
return {
'sent_xml': xml_send,
'received_xml': e.fault.faultstring,
'object': None
}
response, obj = sanitize_response(response)
response, obj = sanitize_response(response.encode('utf-8'))
return {
'sent_xml': xml_send,
'received_xml': response,
'received_xml': response.decode(),
'object': obj
}

2
pytrustnfe/nfse/susesu/__init__.py

@ -29,7 +29,7 @@ def _send(method, **kwargs):
'sent_xml': xml_send,
'received_xml': e.fault.faultstring,
}
result = unicode(result)
result = str(result)
result = unicodedata.normalize('NFKD', result).encode('ascii', 'ignore')
return {
'sent_xml': xml_send,

1
pytrustnfe/test/XMLs/paulistana_resultado.xml

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><RetornoEnvioLoteRPS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.prefeitura.sp.gov.br/nfe"><Cabecalho Versao="1" xmlns=""><Sucesso>true</Sucesso><InformacoesLote><NumeroLote>2654364</NumeroLote><InscricaoPrestador>51212</InscricaoPrestador><CPFCNPJRemetente><CNPJ>21332900163</CNPJ></CPFCNPJRemetente><DataEnvioLote>2016-08-29T10:52:15</DataEnvioLote><QtdNotasProcessadas>1</QtdNotasProcessadas><TempoProcessamento>0</TempoProcessamento><ValorTotalServicos>1.35</ValorTotalServicos></InformacoesLote></Cabecalho><ChaveNFeRPS xmlns=""><ChaveNFe><InscricaoPrestador>52382</InscricaoPrestador><NumeroNFe>446</NumeroNFe><CodigoVerificacao>APR9MJR</CodigoVerificacao></ChaveNFe><ChaveRPS><InscricaoPrestador>51282</InscricaoPrestador><SerieRPS>1</SerieRPS><NumeroRPS>6</NumeroRPS></ChaveRPS></ChaveNFeRPS></RetornoEnvioLoteRPS>

19
pytrustnfe/utils.py

@ -13,6 +13,7 @@ class CabecalhoSoap(object):
def __init__(self, **kwargs):
self.versao = kwargs.pop('versao', '')
self.estado = kwargs.pop('estado', '')
self.method = kwargs.pop('method', '')
self.soap_action = kwargs.pop('soap_action', '')
@ -85,13 +86,25 @@ def _find_node(xml, node):
def gerar_nfeproc(envio, recibo):
NSMAP = {None: 'http://www.portalfiscal.inf.br/nfe'}
root = ET.Element("nfeProc", versao="3.10", nsmap=NSMAP)
docEnvio = ET.fromstring(envio)
docRecibo = ET.fromstring(recibo)
parser = ET.XMLParser(encoding='utf-8')
docEnvio = ET.fromstring(envio.encode('utf-8'), parser=parser)
docRecibo = ET.fromstring(recibo.encode('utf-8'), parser=parser)
nfe = _find_node(docEnvio, "NFe")
protocolo = _find_node(docRecibo, "protNFe")
if nfe is None or protocolo is None:
return ''
return b''
root.append(nfe)
root.append(protocolo)
return ET.tostring(root)
def gerar_nfeproc_cancel(nfe_proc, cancelamento):
docEnvio = ET.fromstring(nfe_proc)
docCancel = ET.fromstring(cancelamento)
ev_cancelamento = _find_node(docCancel, "retEvento")
if ev_cancelamento is None:
return b''
docEnvio.append(ev_cancelamento)
return ET.tostring(docEnvio)

26
pytrustnfe/xml/__init__.py

@ -2,7 +2,6 @@
# © 2016 Danimar Ribeiro, Trustcode
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import unicodedata
from lxml import etree
from lxml import objectify
@ -41,7 +40,6 @@ def render_xml(path, template_name, remove_empty, **nfe):
env.filters["comma"] = filters.format_with_comma
template = env.get_template(template_name)
xml = template.render(**nfe)
parser = etree.XMLParser(remove_blank_text=True, remove_comments=True,
strip_cdata=False)
@ -53,14 +51,13 @@ def render_xml(path, template_name, remove_empty, **nfe):
if recursively_empty(elem):
parent.remove(elem)
return root
return etree.tostring(root)
for element in root.iter("*"): # remove espaços em branco
if element.text is not None and not element.text.strip():
element.text = None
return etree.tostring(root, encoding=str)
def sanitize_response(response):
response = unicode(response)
response = unicodedata.normalize('NFKD', response).encode('ascii',
'ignore')
tree = etree.fromstring(response)
# Remove namespaces inuteis na resposta
for elem in tree.getiterator():
@ -68,6 +65,19 @@ def sanitize_response(response):
continue
i = elem.tag.find('}')
if i >= 0:
elem.tag = elem.tag[i+1:]
elem.tag = elem.tag[i + 1:]
objectify.deannotate(tree, cleanup_namespaces=True)
return response, objectify.fromstring(etree.tostring(tree))
def recursively_normalize(vals):
for item in vals:
if type(vals[item]) is str:
vals[item] = vals[item].strip()
vals[item] = filters.normalize_str(vals[item])
elif type(vals[item]) is dict:
recursively_normalize(vals[item])
elif type(vals[item]) is list:
for a in vals[item]:
recursively_normalize(a)
return vals

18
pytrustnfe/xml/filters.py

@ -13,24 +13,24 @@ def normalize_str(string):
Remove special characters and strip spaces
"""
if string:
if not isinstance(string, unicode):
string = unicode(string, 'utf-8', 'replace')
if not isinstance(string, str):
string = str(string, 'utf-8', 'replace')
string = string.encode('utf-8')
return normalize(
'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore')
'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore').decode()
return ''
def strip_line_feed(string):
if string:
if not isinstance(string, unicode):
string = unicode(string, 'utf-8', 'replace')
if not isinstance(string, str):
string = str(string, 'utf-8', 'replace')
remap = {
ord(u'\t'): u' ',
ord(u'\n'): u' ',
ord(u'\f'): u' ',
ord(u'\r'): None, # Delete
ord('\t'): ' ',
ord('\n'): ' ',
ord('\f'): ' ',
ord('\r'): None, # Delete
}
return string.translate(remap).strip()
return string

0
pytrustnfe/xml/schemas/enviNFe_v3.10.xsd

0
pytrustnfe/xml/schemas/leiauteNFe_v3.10.xsd

0
pytrustnfe/xml/schemas/nfe_v3.10.xsd

2
pytrustnfe/xml/schemas/tiposBasico_v3.10.xsd

@ -494,7 +494,7 @@
</xs:annotation>
<xs:restriction base="xs:string">
<xs:whiteSpace value="preserve"/>
<xs:pattern value="[!-ÿ]{1}[ -ÿ]{0,}[!-ÿ]{1}|[!-ÿ]{1}"/>
<xs:pattern value="[!-ÿ]{1}[ -ÿ]{0,}[!-ÿ]{1}|[!-ÿ]{1}|[!-ÿ]{2}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="TData">

0
pytrustnfe/xml/schemas/xmldsig-core-schema_v1.01.xsd

31
pytrustnfe/xml/validate.py

@ -3,39 +3,16 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import os
import re
from lxml import etree
PATH = os.path.dirname(os.path.abspath(__file__))
SCHEMA = os.path.join(PATH, 'schemas/nfe_v3.10.xsd')
SCHEMA = os.path.join(PATH, 'schemas/enviNFe_v3.10.xsd')
def pop_encoding(xml):
xml = xml.split('\n')
if re.match(r'<\?xml version=', xml[0]):
xml.pop(0)
return '\n'.join(xml)
def valida_nfe(nfe):
xml = pop_encoding(nfe).encode('utf-8')
nfe = etree.fromstring(xml)
def valida_nfe(xml_nfe):
nfe = etree.fromstring(xml_nfe)
esquema = etree.XMLSchema(etree.parse(SCHEMA))
esquema.validate(nfe)
erros = [x.message for x in esquema.error_log]
error_msg = '{field} inválido: {valor}.'
unexpected = '{unexpected} não é esperado. O valor esperado é {expected}'
namespace = '{http://www.portalfiscal.inf.br/nfe}'
mensagens = []
for erro in erros:
campo = re.findall(r"'([^']*)'", erro)[0]
nome = campo[campo.find('}') + 1: ]
valor = nfe.find('.//' + campo).text
if 'Expected is' in erro:
expected_name = re.findall('\(.*?\)', erro)
valor = unexpected.format(unexpected=nome, expected=expected_name)
mensagem = error_msg.format(field=campo.replace(namespace, ''),
valor=valor)
mensagens.append(mensagem)
return "\n".join(mensagens)
return "\n".join(erros)

24
requirements.txt

@ -1,16 +1,18 @@
lxml >= 3.5.0, < 4
nose
mock
lxml >= 3.5.0, < 5
coveralls
http://xmlsoft.org/sources/python/libxml2-python-2.6.21.tar.gz
https://github.com/odoo-brazil/pyxmlsec/archive/master.zip
Jinja2
signxml
suds >= 0.4
suds_requests >= 0.3
defusedxml >= 0.4.1, < 0.6
eight >= 0.3.0, < 0.5
cryptography >= 1.8, < 1.10
pyOpenSSL >= 16.0.0, < 17
urllib3 >= 1.22
suds-jurko >= 0.6
suds-jurko-requests >= 1.1
defusedxml >= 0.4.1, < 1
eight >= 0.3.0, < 1
cryptography >= 1.8, < 3
pyOpenSSL >= 16.0.0, < 18
certifi >= 2015.11.20.1
xmlsec >= 1.3.3
reportlab
pytest
pytest-cov
pytz
zeep

32
setup.py

@ -1,33 +1,44 @@
# coding=utf-8
from setuptools import setup, find_packages
VERSION = "0.1.44"
VERSION = "0.9.24"
setup(
name="PyTrustNFe",
name="PyTrustNFe3",
version=VERSION,
author="Danimar Ribeiro",
author_email='danimaribeiro@gmail.com',
keywords=['nfe', 'mdf-e'],
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 4 - Beta',
'Environment :: Plugins',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Lesser General Public License v2 or \
later (LGPLv2+)',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.4',
'Topic :: Software Development :: Libraries :: Python Modules',
],
packages=find_packages(exclude=['*test*']),
package_data={'pytrustnfe': [
'nfe/templates/*xml',
'nfe/fonts/*ttf',
'nfse/paulistana/templates/*xml',
'nfse/dsf/templates/*xml',
'nfse/ginfes/templates/*xml',
'nfse/simpliss/templates/*xml',
'nfse/betha/templates/*xml',
'nfse/susesu/templates/*xml',
'nfse/imperial/templates/*xml',
'nfse/floripa/templates/*xml',
'nfse/carioca/templates/*xml',
'nfse/bh/templates/*xml',
'nfse/mga/templates/*xml',
'xml/schemas/*xsd',
]},
url='https://github.com/danimaribeiro/PyTrustNFe',
@ -36,15 +47,16 @@ later (LGPLv2+)',
long_description=open('README.md', 'r').read(),
install_requires=[
'Jinja2 >= 2.8',
'pyOpenSSL >= 16.0.0, < 18',
'signxml >= 2.4.0',
'lxml >= 3.5.0, < 4',
'suds >= 0.4',
'suds_requests >= 0.3',
'reportlab'
'lxml >= 3.5.0, < 5',
'suds-jurko >= 0.6',
'suds-jurko-requests >= 1.2',
'reportlab',
'pytz',
'zeep',
],
test_suite='nose.collector',
tests_require=[
'nose',
'mock',
'pytest',
],
)

0
pytrustnfe/test/XMLs/NFe00000857.xml → tests/XMLs/NFe00000857.xml

0
pytrustnfe/test/XMLs/jinja_remove_empty.xml → tests/XMLs/jinja_remove_empty.xml

0
pytrustnfe/test/XMLs/jinja_result.xml → tests/XMLs/jinja_result.xml

0
pytrustnfe/test/XMLs/jinja_template.xml → tests/XMLs/jinja_template.xml

0
pytrustnfe/test/XMLs/paulistana_canc.xml → tests/XMLs/paulistana_canc.xml

1
pytrustnfe/test/XMLs/paulistana_canc_errado.xml → tests/XMLs/paulistana_canc_errado.xml

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<RetornoCancelamentoNFe
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"

1
pytrustnfe/test/XMLs/paulistana_canc_ok.xml → tests/XMLs/paulistana_canc_ok.xml

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<RetornoCancelamentoNFe
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"

1
tests/XMLs/paulistana_resultado.xml

@ -0,0 +1 @@
<RetornoEnvioLoteRPS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.prefeitura.sp.gov.br/nfe"><Cabecalho Versao="1" xmlns=""><Sucesso>true</Sucesso><InformacoesLote><NumeroLote>2654364</NumeroLote><InscricaoPrestador>51212</InscricaoPrestador><CPFCNPJRemetente><CNPJ>21332900163</CNPJ></CPFCNPJRemetente><DataEnvioLote>2016-08-29T10:52:15</DataEnvioLote><QtdNotasProcessadas>1</QtdNotasProcessadas><TempoProcessamento>0</TempoProcessamento><ValorTotalServicos>1.35</ValorTotalServicos></InformacoesLote></Cabecalho><ChaveNFeRPS xmlns=""><ChaveNFe><InscricaoPrestador>52382</InscricaoPrestador><NumeroNFe>446</NumeroNFe><CodigoVerificacao>APR9MJR</CodigoVerificacao></ChaveNFe><ChaveRPS><InscricaoPrestador>51282</InscricaoPrestador><SerieRPS>1</SerieRPS><NumeroRPS>6</NumeroRPS></ChaveRPS></ChaveNFeRPS></RetornoEnvioLoteRPS>

16
pytrustnfe/test/XMLs/paulistana_signature.xml → tests/XMLs/paulistana_signature.xml

@ -1,8 +1,4 @@
<?xml version="1.0"?>
<PedidoEnvioLoteRPS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.prefeitura.sp.gov.br/nfe"><Cabecalho xmlns="" Versao="1"><CPFCNPJRemetente><CNPJ>12345678901234</CNPJ></CPFCNPJRemetente><transacao>false</transacao><dtInicio>2016-08-29</dtInicio><dtFim>2016-08-29</dtFim><QtdRPS>1</QtdRPS><ValorTotalServicos/><ValorTotalDeducoes/></Cabecalho><RPS xmlns=""><Assinatura>E4fpHYkQa7Naxn6IKGb7NwwZu5tPk/KXJ9hCwtZgq0xvKS450aQqqBL+7Iv46lTgqrSMu7+gLrl+LC1qs/8aT2mbHE8uaVFSbzwZ+sF/BkcT6nsFHLMswEiTAEs95Jb7hN1cC91xqQGRH4buw0TzxHKmhuLJ22WwtG/scxyKtjM=</Assinatura><ChaveRPS><InscricaoPrestador>123456</InscricaoPrestador><SerieRPS>1</SerieRPS><NumeroRPS>1</NumeroRPS></ChaveRPS><TipoRPS>RPS</TipoRPS><DataEmissao>2016-08-29</DataEmissao><StatusRPS>N</StatusRPS><TributacaoRPS>T</TributacaoRPS><ValorServicos/><ValorDeducoes/><ValorPIS>0.00</ValorPIS><ValorCOFINS>0.00</ValorCOFINS><ValorINSS>0.00</ValorINSS><ValorIR>0.00</ValorIR><ValorCSLL>0.00</ValorCSLL><CodigoServico>07498</CodigoServico><AliquotaServicos>5.00</AliquotaServicos><ISSRetido>false</ISSRetido><CPFCNPJTomador>
</CPFCNPJTomador><InscricaoMunicipalTomador>123456</InscricaoMunicipalTomador><RazaoSocialTomador>Trustcode</RazaoSocialTomador><EnderecoTomador><TipoLogradouro>1</TipoLogradouro><Logradouro>Vinicius de Moraes, 42</Logradouro><NumeroEndereco>42</NumeroEndereco><ComplementoEndereco/><Bairro>Corrego</Bairro><Cidade>Floripa</Cidade><UF>SC</UF><CEP>88037240</CEP></EnderecoTomador><Discriminacao>Venda de servico</Discriminacao></RPS><Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<PedidoEnvioLoteRPS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.prefeitura.sp.gov.br/nfe"><Cabecalho xmlns="" Versao="1"><CPFCNPJRemetente><CNPJ>12345678901234</CNPJ></CPFCNPJRemetente><transacao>false</transacao><dtInicio>2016-08-29</dtInicio><dtFim>2016-08-29</dtFim><QtdRPS>1</QtdRPS><ValorTotalServicos/><ValorTotalDeducoes/></Cabecalho><RPS xmlns=""><Assinatura>E4fpHYkQa7Naxn6IKGb7NwwZu5tPk/KXJ9hCwtZgq0xvKS450aQqqBL+7Iv46lTgqrSMu7+gLrl+LC1qs/8aT2mbHE8uaVFSbzwZ+sF/BkcT6nsFHLMswEiTAEs95Jb7hN1cC91xqQGRH4buw0TzxHKmhuLJ22WwtG/scxyKtjM=</Assinatura><ChaveRPS><InscricaoPrestador>123456</InscricaoPrestador><SerieRPS>1</SerieRPS><NumeroRPS>1</NumeroRPS></ChaveRPS><TipoRPS>RPS</TipoRPS><DataEmissao>2016-08-29</DataEmissao><StatusRPS>N</StatusRPS><TributacaoRPS>T</TributacaoRPS><ValorServicos/><ValorDeducoes/><ValorPIS>0.00</ValorPIS><ValorCOFINS>0.00</ValorCOFINS><ValorINSS>0.00</ValorINSS><ValorIR>0.00</ValorIR><ValorCSLL>0.00</ValorCSLL><CodigoServico>07498</CodigoServico><AliquotaServicos>5.00</AliquotaServicos><ISSRetido>false</ISSRetido><CPFCNPJTomador/><InscricaoMunicipalTomador>123456</InscricaoMunicipalTomador><RazaoSocialTomador>Trustcode</RazaoSocialTomador><EnderecoTomador><TipoLogradouro>1</TipoLogradouro><Logradouro>Vinicius de Moraes, 42</Logradouro><NumeroEndereco>42</NumeroEndereco><ComplementoEndereco/><Bairro>Corrego</Bairro><Cidade>Floripa</Cidade><UF>SC</UF><CEP>88037240</CEP></EnderecoTomador><Discriminacao>Venda de servico</Discriminacao></RPS><Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
@ -12,12 +8,12 @@
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>ivaOwkcrt0pfuMYsAdfyLaUAcIk=</DigestValue>
<DigestValue>ePJnD6hyDvlJo08PFX8h2TXk0ZM=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>FjIHdfPavSEyaWYhAT0z0shPLuTsqBKyy78PUEZ8PUhTZ+iSV0MOvAIRq9MPPVK9
jjXOw1TE903uSK8aJon52RNKPd68ORVJ3bKFSjTqQLxFRR9tiiAQFrWDETf7FF89
EhG6dy6TGcgVbOyn0Jqm8MkqrE1XrJ44orN1X+Jt+7U=</SignatureValue>
<SignatureValue>GbaQaTEtxuKdRRaadginWPFH5K65ywqEikkwChWO3xX5Kglq8RPm4+LjnpJmuTcE
9I2BVon3GJFh+c/6RKzJPose6FXog2xnCpTOgwA/rks/gKsUAaRlXCPsLcKMKaOj
3eH21RHEyrxBAbdpEUdlEgQWaWzmGq009EiQ544sD6c=</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIICMTCCAZqgAwIBAgIQfYOsIEVuAJ1FwwcTrY0t1DANBgkqhkiG9w0BAQUFADBX
@ -34,4 +30,4 @@ QtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86
d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw==</X509Certificate>
</X509Data>
</KeyInfo>
</Signature></PedidoEnvioLoteRPS>
</Signature></PedidoEnvioLoteRPS>

0
pytrustnfe/test/XMLs/recibo_envio_1.xml → tests/XMLs/recibo_envio_1.xml

0
pytrustnfe/test/XMLs/recibo_envio_2.xml → tests/XMLs/recibo_envio_2.xml

0
pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml → tests/XMLs/recibo_protocolo_sucesso_1.xml

0
pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml → tests/XMLs/recibo_protocolo_sucesso_2.xml

0
pytrustnfe/test/__init__.py → tests/__init__.py

6
pytrustnfe/test/test_add_qr_code.py → tests/test_add_qr_code.py

@ -9,10 +9,10 @@ from pytrustnfe.nfe import _add_qrCode
class TestAddQRCode(unittest.TestCase):
def setUp(self):
self.xml_sem_qrcode = open('pytrustnfe/test/xml_sem_qrcode.xml', 'r')
self.xml_com_qrcode = open('pytrustnfe/test/xml_com_qrcode.xml', 'r')
self.xml_sem_qrcode = open('tests/xml_sem_qrcode.xml', 'r')
self.xml_com_qrcode = open('tests/xml_com_qrcode.xml', 'r')
dhEmi = '2016-11-09T16:03:25-00:00'
chave_nfe = u'NFe35161121332917000163650010000000011448875034'
chave_nfe = 'NFe35161121332917000163650010000000011448875034'
ambiente = 2
valor_total = '324.00'
icms_total = '61.56'

12
pytrustnfe/test/test_assinatura.py → tests/test_assinatura.py

@ -11,16 +11,14 @@ from lxml import etree
from pytrustnfe.nfe.assinatura import Assinatura
XML_ASSINAR = '<?xml version="1.0" encoding="UTF-8"?>' \
'<Envelope xmlns="urn:envelope">' \
XML_ASSINAR = '<Envelope xmlns="urn:envelope">' \
' <Data Id="NFe43150602261542000143550010000000761792265342">'\
' Hello, World!' \
' </Data>' \
'</Envelope>'
XML_ERRADO = '<?xml version="1.0" encoding="UTF-8"?>' \
'<Envelope xmlns="urn:envelope">' \
XML_ERRADO = '<Envelope xmlns="urn:envelope">' \
' <Data Id="NFe">' \
' Hello, World!' \
' </Data>' \
@ -32,21 +30,21 @@ class test_assinatura(unittest.TestCase):
caminho = os.path.dirname(__file__)
def test_assinar_xml_senha_invalida(self):
pfx = open(os.path.join(self.caminho, 'teste.pfx')).read()
pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
signer = Assinatura(pfx, '123')
self.assertRaises(Exception, signer.assina_xml, signer,
etree.fromstring(XML_ASSINAR),
'NFe43150602261542000143550010000000761792265342')
def test_assinar_xml_invalido(self):
pfx = open(os.path.join(self.caminho, 'teste.pfx')).read()
pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
signer = Assinatura(pfx, '123456')
self.assertRaises(Exception, signer.assina_xml, signer,
etree.fromstring(XML_ERRADO),
'NFe43150602261542000143550010000000761792265342')
def test_assinar_xml_valido(self):
pfx = open(os.path.join(self.caminho, 'teste.pfx')).read()
pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
signer = Assinatura(pfx, '123456')
xml = signer.assina_xml(
etree.fromstring(XML_ASSINAR),

8
pytrustnfe/test/test_certificado.py → tests/test_certificado.py

@ -49,21 +49,21 @@ class test_assinatura(unittest.TestCase):
caminho = os.path.dirname(__file__)
def test_preparar_pfx(self):
dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
cert, key = extract_cert_and_key_from_pfx(dir_pfx, '123456')
self.assertEqual(key, CHAVE, 'Chave gerada inválida')
self.assertEqual(cert, CERTIFICADO, 'Certificado inválido')
def test_save_pfx(self):
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
pfx = Certificado(pfx_source, '123')
path = pfx.save_pfx()
saved = open(path, 'r').read()
saved = open(path, 'rb').read()
self.assertEqual(pfx_source, saved,
'Arquivo pfx salvo não bate com arquivo lido')
def test_save_cert_and_key(self):
dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
dir_pfx = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
cert, key = extract_cert_and_key_from_pfx(dir_pfx, '123456')
cert_path, key_path = save_cert_key(cert, key)
cert_saved = open(cert_path, 'r').read()

0
pytrustnfe/test/test_comunicacao.py → tests/test_comunicacao.py

2
pytrustnfe/test/test_consulta_cadastro.py → tests/test_consulta_cadastro.py

@ -12,7 +12,7 @@ class test_consulta_cadastro(unittest.TestCase):
caminho = os.path.dirname(__file__)
def test_conta_de_cadastro(self):
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
pfx = Certificado(pfx_source, '123456')
obj = {'cnpj': '12345678901234', 'estado': '42'}

2
pytrustnfe/test/test_danfe.py → tests/test_danfe.py

@ -22,5 +22,5 @@ class test_danfe(unittest.TestCase):
# Para testar localmente o Danfe
# with open('/home/danimar/danfe.pdf', 'w') as oFile:
with tempfile.TemporaryFile(mode='w') as oFile:
with tempfile.TemporaryFile(mode='wb') as oFile:
oDanfe.writeto_pdf(oFile)

2
pytrustnfe/test/test_ginfes.py → tests/test_ginfes.py

@ -13,7 +13,7 @@ class test_nfse_ginfes(unittest.TestCase):
@unittest.skip
def test_consulta_situacao_lote(self):
pfx_source = open('/home/danimar/Downloads/machado.pfx', 'r').read()
pfx_source = open('/home/danimar/Downloads/machado.pfx', 'rb').read()
pfx = Certificado(pfx_source, '123456789')
dados = {'ambiente': 'homologacao'}

8
pytrustnfe/test/test_nfse_paulistana.py → tests/test_nfse_paulistana.py

@ -54,7 +54,7 @@ class test_nfse_paulistana(unittest.TestCase):
return nfse
def test_envio_nfse(self):
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
pfx = Certificado(pfx_source, '123456')
nfse = self._get_nfse()
@ -77,7 +77,7 @@ class test_nfse_paulistana(unittest.TestCase):
retorno['object'].ChaveNFeRPS.ChaveRPS.NumeroRPS, 6)
def test_nfse_signature(self):
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
pfx = Certificado(pfx_source, '123456')
nfse = self._get_nfse()
@ -103,7 +103,7 @@ class test_nfse_paulistana(unittest.TestCase):
}
def test_cancelamento_nfse_ok(self):
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
pfx = Certificado(pfx_source, '123456')
cancelamento = self._get_cancelamento()
@ -122,7 +122,7 @@ class test_nfse_paulistana(unittest.TestCase):
self.assertEqual(retorno['object'].Cabecalho.Sucesso, True)
def test_cancelamento_nfse_com_erro(self):
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'r').read()
pfx_source = open(os.path.join(self.caminho, 'teste.pfx'), 'rb').read()
pfx = Certificado(pfx_source, '123456')
cancelamento = self._get_cancelamento()

0
pytrustnfe/test/test_servidores.py → tests/test_servidores.py

16
pytrustnfe/test/test_utils.py → tests/test_utils.py

@ -60,7 +60,7 @@ class test_utils(unittest.TestCase):
chave.validar()
chave.cnpj = '1234567891011'
self.assertEqual('CNPJ necessário para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')
with self.assertRaises(AssertionError) as cm:
@ -68,7 +68,7 @@ class test_utils(unittest.TestCase):
chave.validar()
chave.estado = '42'
self.assertEqual('Estado necessário para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')
with self.assertRaises(AssertionError) as cm:
@ -76,7 +76,7 @@ class test_utils(unittest.TestCase):
chave.validar()
chave.emissao = '0'
self.assertEqual('Emissão necessário para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')
with self.assertRaises(AssertionError) as cm:
@ -84,7 +84,7 @@ class test_utils(unittest.TestCase):
chave.validar()
chave.modelo = '55'
self.assertEqual('Modelo necessário para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')
with self.assertRaises(AssertionError) as cm:
@ -92,7 +92,7 @@ class test_utils(unittest.TestCase):
chave.validar()
chave.serie = '012'
self.assertEqual('Série necessária para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')
with self.assertRaises(AssertionError) as cm:
@ -100,7 +100,7 @@ class test_utils(unittest.TestCase):
chave.validar()
chave.numero = '000000780'
self.assertEqual('Número necessário para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')
with self.assertRaises(AssertionError) as cm:
@ -108,12 +108,12 @@ class test_utils(unittest.TestCase):
chave.validar()
chave.tipo = '42'
self.assertEqual('Tipo necessário para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')
with self.assertRaises(AssertionError) as cm:
chave.codigo = ''
chave.validar()
self.assertEqual('Código necessário para criar chave NF-e',
cm.exception.message,
str(cm.exception),
'Validação da chave nf-e incorreta')

4
pytrustnfe/test/test_xml.py → tests/test_xml.py

@ -26,5 +26,5 @@ class test_xmlfilters(unittest.TestCase):
self.assertEqual('2016-09-17', format_date(dt.date()))
self.assertEqual('2016-09-17T12:12:12', format_datetime(dt))
word = strip_line_feed(u"olá\ncomo vai\r senhor ")
self.assertEqual(word, u"olá como vai senhor")
word = strip_line_feed("olá\ncomo vai\r senhor ")
self.assertEqual(word, "olá como vai senhor")

5
pytrustnfe/test/test_xml_serializacao.py → tests/test_xml_serializacao.py

@ -15,13 +15,13 @@ class test_xml_serializacao(unittest.TestCase):
tag2='ola', tag3='comovai')
result = open(os.path.join(path, 'jinja_result.xml'), 'r').read()
self.assertEqual(xml + '\n', result)
self.assertEqual(xml + "\n", result)
def test_serializacao_remove_empty(self):
path = os.path.join(os.path.dirname(__file__), 'XMLs')
xmlElem = render_xml(path, 'jinja_template.xml', True, tag1='oi',
tag2='ola', tag3='comovai')
xml = etree.tostring(xmlElem)
xml = etree.tostring(xmlElem, encoding=str)
result = open(os.path.join(path, 'jinja_remove_empty.xml'), 'r').read()
self.assertEqual(xml + '\n', result)
@ -29,7 +29,6 @@ class test_xml_serializacao(unittest.TestCase):
path = os.path.join(os.path.dirname(__file__), 'XMLs')
xml_to_clear = open(os.path.join(path, 'jinja_result.xml'), 'r').read()
xml, obj = sanitize_response(xml_to_clear)
self.assertEqual(xml, xml_to_clear)
self.assertEqual(obj.tpAmb, 'oi')
self.assertEqual(obj.CNPJ, 'ola')

0
pytrustnfe/test/teste.pfx → tests/teste.pfx

0
pytrustnfe/test/xml_assinado.xml → tests/xml_assinado.xml

6
pytrustnfe/test/xml_com_qrcode.xml → tests/xml_com_qrcode.xml

@ -30,11 +30,11 @@
<xNome>LEL AMBIENTAL LTDA - EPP</xNome>
<xFant>Zell Ambiental</xFant>
<enderEmit>
<xLgr>Rua Padre Jo&#227;o</xLgr>
<xLgr>Rua Padre João</xLgr>
<nro>444</nro>
<xBairro>Penha de Fran&#231;a</xBairro>
<xBairro>Penha de França</xBairro>
<cMun>3550308</cMun>
<xMun>S&#227;o Paulo</xMun>
<xMun>São Paulo</xMun>
<UF>SP</UF>
<CEP>03637000</CEP>
<cPais>1058</cPais>

6
pytrustnfe/test/xml_sem_qrcode.xml → tests/xml_sem_qrcode.xml

@ -31,11 +31,11 @@
<xNome>LEL AMBIENTAL LTDA - EPP</xNome>
<xFant>Zell Ambiental</xFant>
<enderEmit>
<xLgr>Rua Padre Jo&#xE3;o</xLgr>
<xLgr>Rua Padre João</xLgr>
<nro>444</nro>
<xBairro>Penha de Fran&#xE7;a</xBairro>
<xBairro>Penha de França</xBairro>
<cMun>3550308</cMun>
<xMun>S&#xE3;o Paulo</xMun>
<xMun>São Paulo</xMun>
<UF>SP</UF>
<CEP>03637000</CEP>
<cPais>1058</cPais>

0
pytrustnfe/test/xml_valido_assinado.xml → tests/xml_valido_assinado.xml

Loading…
Cancel
Save