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