Skip to content

Commit 0499709

Browse files
Merge pull request erpbrasil#74 from Engenere/add-sinc-e-montar-proc
Melhorias para NF-e Desativa por padrão a "consulta do status do serviço" e a "consulta da situação da nota" antes enviar. Possibilidade de poder escolher a forma de transmissão sincrona. Adiciona uma função auxiliar para montar o arquivo final da NF-e.
2 parents 5e01ece + 19a5636 commit 0499709

File tree

4 files changed

+127
-35
lines changed

4 files changed

+127
-35
lines changed

src/erpbrasil/edoc/edoc.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ class DocumentoEletronico(ABC):
3434
_consulta_servico_ao_enviar = False
3535
_consulta_documento_antes_de_enviar = False
3636

37-
def __init__(self, transmissao):
37+
def __init__(self, transmissao, envio_sincrono=False):
3838
self._transmissao = transmissao
39+
self.envio_sincrono = bool(envio_sincrono)
3940

4041
def _generateds_to_string_etree(self, ds, pretty_print=False):
4142
if type(ds) == _Element:
@@ -68,7 +69,7 @@ def _post(self, raiz, url, operacao, classe):
6869
retorno = self._transmissao.enviar(operacao, xml_etree)
6970
return analisar_retorno_raw(operacao, raiz, xml_string, retorno, classe)
7071

71-
def processar_documento(self, edoc):
72+
def processar_documento(self, edoc, envio_sincrono=False):
7273
"""Processar documento executa o envio do documento fiscal de forma
7374
completa ao serviço relacionado, esta é um método padrão que
7475
segue o seguinte workflow:
@@ -132,18 +133,19 @@ def processar_documento(self, edoc):
132133
#
133134

134135
proc_envio = self.envia_documento(edoc)
136+
if self.envio_sincrono:
137+
self.monta_processo(edoc, proc_envio)
135138
yield proc_envio
136139

137-
#
138-
# Deu errado?
139-
#
140-
if not proc_envio.resposta:
141-
return
142-
143-
if not self._verifica_resposta_envio_sucesso(proc_envio):
144-
#
145-
# Interrompe o processo
146-
#
140+
# Retorna imediatamente se alguma das condições abaixo for verdadeira:
141+
# 1. A resposta do processo de envio é falsa.
142+
# 2. A resposta do envio não indica sucesso.
143+
# 3. O envio é síncrono (não é necessário consultar o recibo).
144+
if (
145+
not proc_envio.resposta
146+
or not self._verifica_resposta_envio_sucesso(proc_envio)
147+
or self.envio_sincrono
148+
):
147149
return
148150

149151
#

src/erpbrasil/edoc/nfce.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,9 @@ def __init__(
258258
qrcode_versao="2",
259259
csc_token=None,
260260
csc_code=None,
261+
envio_sincrono=True,
261262
):
262-
super().__init__(transmissao, uf, versao, ambiente)
263-
self.mod = str(mod)
263+
super().__init__(transmissao, uf, versao, ambiente, mod, envio_sincrono)
264264
self.qrcode_versao = str(qrcode_versao)
265265
self.csc_token = str(csc_token)
266266
self.csc_code = str(csc_code)
@@ -333,7 +333,7 @@ def envia_documento(self, edoc):
333333
raiz = retEnviNFe.TEnviNFe(
334334
versao=self.versao,
335335
idLote=datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
336-
indSinc="1",
336+
indSinc="1" if self.envio_sincrono else "0",
337337
)
338338
raiz.original_tagname_ = "enviNFe"
339339
xml_envio_string, xml_envio_etree = self._generateds_to_string_etree(raiz)

src/erpbrasil/edoc/nfe.py

+54-20
Original file line numberDiff line numberDiff line change
@@ -717,13 +717,25 @@ def localizar_url(servico, estado, mod="55", ambiente=2):
717717
class NFe(DocumentoEletronico):
718718
_namespace = "http://www.portalfiscal.inf.br/nfe"
719719
_edoc_situacao_arquivo_recebido_com_sucesso = "103"
720+
_edoc_situacao_arquivo_processado_com_sucesso = "104"
720721
_edoc_situacao_servico_em_operacao = "107"
721-
_consulta_servico_ao_enviar = True
722-
_consulta_documento_antes_de_enviar = True
722+
723+
# Desativado por padrão para evitar 'consumo indevido'
724+
_consulta_servico_ao_enviar = False
725+
_consulta_documento_antes_de_enviar = False
726+
723727
_maximo_tentativas_consulta_recibo = 5
724728

725-
def __init__(self, transmissao, uf, versao="4.00", ambiente="2", mod="55"):
726-
super().__init__(transmissao)
729+
def __init__(
730+
self,
731+
transmissao,
732+
uf,
733+
versao="4.00",
734+
ambiente="2",
735+
mod="55",
736+
envio_sincrono=False,
737+
):
738+
super().__init__(transmissao, envio_sincrono)
727739
self.versao = str(versao)
728740
self.ambiente = str(ambiente)
729741
self.uf = int(uf)
@@ -754,6 +766,7 @@ def status_servico(self):
754766
)
755767

756768
def consulta_documento(self, chave):
769+
# NfeConsultaProtocolo
757770
raiz = retConsSitNFe.TConsSitNFe(
758771
versao=self.versao,
759772
tpAmb=self.ambiente,
@@ -784,14 +797,11 @@ def envia_documento(self, edoc):
784797
raiz = retEnviNFe.TEnviNFe(
785798
versao=self.versao,
786799
idLote=datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
787-
indSinc="0",
800+
indSinc="1" if self.envio_sincrono else "0",
788801
)
789802
raiz.original_tagname_ = "enviNFe"
790803
xml_envio_string, xml_envio_etree = self._generateds_to_string_etree(raiz)
791804
xml_envio_etree.append(etree.fromstring(xml_assinado))
792-
793-
# teste_string, teste_etree = self._generateds_to_string_etree(xml_envio_etree)
794-
795805
return self._post(
796806
xml_envio_etree,
797807
# 'https://hom.sefazvirtual.fazenda.gov.br/NFeAutorizacao4/NFeAutorizacao4.asmx?wsdl',
@@ -953,15 +963,18 @@ def _verifica_documento_ja_enviado(self, proc_consulta):
953963
return False
954964

955965
def _verifica_resposta_envio_sucesso(self, proc_envio):
956-
if (
957-
proc_envio.resposta.cStat
958-
== self._edoc_situacao_arquivo_recebido_com_sucesso
959-
):
960-
return True
961-
return False
966+
"""
967+
Verifica se a resposta do envio indica sucesso:
968+
- cStat "103" = "Lote recebido com sucesso" (assíncrono)
969+
- cStat "104" = "Lote processado com sucesso" (síncrono)
970+
"""
971+
return proc_envio.resposta.cStat in [
972+
self._edoc_situacao_arquivo_recebido_com_sucesso,
973+
self._edoc_situacao_arquivo_processado_com_sucesso,
974+
]
962975

963976
def _aguarda_tempo_medio(self, proc_envio):
964-
time.sleep(float(proc_envio.resposta.infRec.tMed) * 1.3)
977+
time.sleep(float(proc_envio.resposta.infRec.tMed))
965978

966979
def _edoc_situacao_em_processamento(self, proc_recibo):
967980
if proc_recibo.resposta.cStat == "105":
@@ -1017,9 +1030,14 @@ def consultar_distribuicao(
10171030
retDistDFeInt,
10181031
)
10191032

1020-
def monta_processo(self, edoc, proc_envio, proc_recibo):
1033+
def monta_processo(self, edoc, proc_envio, proc_recibo=None):
10211034
nfe = proc_envio.envio_raiz.find("{" + self._namespace + "}NFe")
1022-
protocolos = proc_recibo.resposta.protNFe
1035+
if proc_recibo:
1036+
protocolos = proc_recibo.resposta.protNFe
1037+
else:
1038+
# A falta do recibo indica envio no modo síncrono
1039+
# o protocolo é recuperado diretamente da resposta do envio.
1040+
protocolos = proc_envio.resposta.protNFe
10231041
if len(nfe) and protocolos:
10241042
if not isinstance(protocolos, list):
10251043
protocolos = [protocolos]
@@ -1032,11 +1050,27 @@ def monta_processo(self, edoc, proc_envio, proc_recibo):
10321050
xml_file, nfe_proc = self._generateds_to_string_etree(nfe_proc)
10331051
prot_nfe = nfe_proc.find("{" + self._namespace + "}protNFe")
10341052
prot_nfe.addprevious(nfe)
1035-
proc_recibo.processo = nfe_proc
1036-
proc_recibo.processo_xml = self._generateds_to_string_etree(nfe_proc)[0]
1037-
proc_recibo.protocolo = protocolo
1053+
1054+
proc = proc_recibo if proc_recibo else proc_envio
1055+
proc.processo = nfe_proc
1056+
proc.processo_xml = self._generateds_to_string_etree(nfe_proc)[0]
1057+
proc.protocolo = protocolo
10381058
return True
10391059

1060+
def monta_nfe_proc(self, nfe, prot_nfe):
1061+
"""
1062+
Constrói e retorna o XML do processo da NF-e,
1063+
incorporando a NF-e com o seu protocolo de autorização.
1064+
"""
1065+
nfe_proc = etree.Element(
1066+
f"{{{self._namespace}}}nfeProc",
1067+
versao=self.versao,
1068+
nsmap={None: self._namespace},
1069+
)
1070+
nfe_proc.append(nfe)
1071+
nfe_proc.append(prot_nfe)
1072+
return etree.tostring(nfe_proc)
1073+
10401074
def consultar_cadastro(self, uf, cnpj=None, cpf=None, ie=None):
10411075
if not cnpj and not cpf and not ie:
10421076
return

tests/test_erpbrasil_edoc_nfe.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from unittest import TestCase
2+
3+
from erpbrasil.edoc.nfe import NFe
4+
from lxml import etree
5+
6+
7+
class NFeTests(TestCase):
8+
def setUp(self):
9+
self.nfe = NFe(False, "35", versao="4.00", ambiente="1")
10+
nfe_xml_str = """
11+
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
12+
<infNFe Id="NFe12345678901234567890123456789012345678901234" versao="4.00">
13+
<ide>
14+
<cUF>35</cUF>
15+
<cNF>1234567</cNF>
16+
<natOp>Venda de mercadoria</natOp>
17+
<!-- Outros campos da tag ide -->
18+
</ide>
19+
<emit>
20+
<CNPJ>12345678000195</CNPJ>
21+
<xNome>Empresa Exemplo</xNome>
22+
<!-- Outros campos da tag emit -->
23+
</emit>
24+
<!-- Outras tags como det, total, transp, etc. -->
25+
</infNFe>
26+
</NFe>
27+
"""
28+
prot_nfe_xml_str = """
29+
<protNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00">
30+
<infProt>
31+
<tpAmb>1</tpAmb>
32+
<verAplic>SP_NFE_PL_008i2</verAplic>
33+
<chNFe>12345678901234567890123456789012345678901234</chNFe>
34+
<dhRecbto>2024-01-16T14:00:00-03:00</dhRecbto>
35+
<nProt>13579024681112</nProt>
36+
<digVal>abcd1234abcd1234abcd1234abcd1234abcd1234=</digVal>
37+
<cStat>100</cStat>
38+
<xMotivo>Autorizado o uso da NF-e</xMotivo>
39+
</infProt>
40+
</protNFe>
41+
"""
42+
self.nfe_element = etree.fromstring(nfe_xml_str)
43+
self.prot_nfe_element = etree.fromstring(prot_nfe_xml_str)
44+
45+
def test_monta_nfe_proc(self):
46+
nfe_proc_bytes = self.nfe.monta_nfe_proc(
47+
self.nfe_element, self.prot_nfe_element
48+
)
49+
root = etree.fromstring(nfe_proc_bytes)
50+
self.assertIsInstance(nfe_proc_bytes, bytes)
51+
self.assertEqual(root.tag, "{http://www.portalfiscal.inf.br/nfe}nfeProc")
52+
children = list(root)
53+
self.assertEqual(len(children), 2)
54+
child_tags = [child.tag for child in children]
55+
self.assertIn("{http://www.portalfiscal.inf.br/nfe}NFe", child_tags)
56+
self.assertIn("{http://www.portalfiscal.inf.br/nfe}protNFe", child_tags)

0 commit comments

Comments
 (0)