Skip to content

Commit 2c099e5

Browse files
[RFC] mdfe: refactor
1 parent e7a0e23 commit 2c099e5

File tree

1 file changed

+162
-146
lines changed

1 file changed

+162
-146
lines changed

src/erpbrasil/edoc/mdfe.py

+162-146
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,34 @@
11
# Copyright (C) 2019 Luis Felipe Mileo - KMEE
22

33

4-
import binascii
5-
import datetime
6-
import time
4+
import base64
5+
import gzip
6+
from contextlib import suppress
77

88
from lxml import etree
99

1010
from erpbrasil.edoc.edoc import DocumentoEletronico
11-
12-
try:
13-
# Consulta Status
14-
# Consulta Não Encerrados
15-
from nfelib.mdfe.bindings.v3_0.cons_mdfe_nao_enc_v3_00 import ConsMdfeNaoEnc
16-
17-
# Consulta Recibo
18-
from nfelib.mdfe.bindings.v3_0.cons_reci_mdfe_v3_00 import ConsReciMdfe
19-
20-
# Consulta Documento
21-
from nfelib.mdfe.bindings.v3_0.cons_sit_mdfe_v3_00 import ConsSitMdfe
22-
from nfelib.mdfe.bindings.v3_0.cons_stat_serv_mdfe_v3_00 import ConsStatServMdfe
23-
24-
# Envio
25-
from nfelib.mdfe.bindings.v3_0.envi_mdfe_v3_00 import EnviMdfe
26-
from nfelib.mdfe.bindings.v3_0.ev_canc_mdfe_v3_00 import EvCancMdfe
27-
from nfelib.mdfe.bindings.v3_0.ev_enc_mdfe_v3_00 import EvEncMdfe
28-
29-
# Eventos
30-
from nfelib.mdfe.bindings.v3_0.evento_mdfe_v3_00 import EventoMdfe
31-
32-
# Processamento
33-
from nfelib.mdfe.bindings.v3_0.proc_mdfe_v3_00 import MdfeProc
34-
from nfelib.mdfe.bindings.v3_0.ret_cons_mdfe_nao_enc_v3_00 import RetConsMdfeNaoEnc
35-
from nfelib.mdfe.bindings.v3_0.ret_cons_reci_mdfe_v3_00 import RetConsReciMdfe
36-
from nfelib.mdfe.bindings.v3_0.ret_cons_sit_mdfe_v3_00 import RetConsSitMdfe
37-
from nfelib.mdfe.bindings.v3_0.ret_cons_stat_serv_mdfe_v3_00 import (
11+
from erpbrasil.transmissao import TransmissaoSOAP
12+
13+
with suppress(ImportError):
14+
from nfelib.mdfe.bindings.v3_0 import (
15+
ConsSitMdfe,
16+
ConsStatServMdfe,
17+
EvCancMdfe,
18+
# EvCceMdfe # Nao tem carta de correcao do MDFe ? pelo jeito nao mas tem alguns tipos de eventos,
19+
EventoMdfe,
20+
EvEncMdfe,
21+
RetConsSitMdfe,
3822
RetConsStatServMdfe,
23+
RetMdfe,
24+
RetEventoMdfe,
3925
)
40-
from nfelib.mdfe.bindings.v3_0.ret_envi_mdfe_v3_00 import RetEnviMdfe
41-
from nfelib.mdfe.bindings.v3_0.ret_evento_mdfe_v3_00 import RetEventoMdfe
42-
except ImportError:
43-
pass
26+
27+
AMBIENTE_PRODUCAO = 1
28+
AMBIENTE_HOMOLOGACAO = 2
4429

4530
WS_MDFE_CONSULTA = "MDFeConsulta"
46-
WS_MDFE_SITUACAO = "MDFeStatusServico"
31+
WS_MDFE_STATUS_SERVICO = "MDFeStatusServicoMDF"
4732
WS_MDFE_CONSULTA_NAO_ENCERRADOS = "MDFeConsNaoEnc"
4833
WS_MDFE_DISTRIBUICAO = "MDFeDistribuicaoDFe"
4934

@@ -52,49 +37,109 @@
5237
WS_MDFE_RET_RECEPCAO = "MDFeRetRecepcao"
5338
WS_MDFE_RECEPCAO_EVENTO = "MDFeRecepcaoEvento"
5439

55-
AMBIENTE_PRODUCAO = 1
56-
AMBIENTE_HOMOLOGACAO = 2
40+
QR_CODE_URL = "QRCode"
5741

5842
MDFE_MODELO = "58"
5943

60-
SVC_RS = {
44+
SIGLA_ESTADO = {
45+
"AC": 12,
46+
"AL": 27,
47+
"AM": 13,
48+
"AP": 16,
49+
"BA": 29,
50+
"CE": 23,
51+
"DF": 53,
52+
"ES": 32,
53+
"GO": 52,
54+
"MA": 21,
55+
"MG": 31,
56+
"MS": 50,
57+
"MT": 51,
58+
"PA": 15,
59+
"PB": 25,
60+
"PE": 26,
61+
"PI": 22,
62+
"PR": 41,
63+
"RJ": 33,
64+
"RN": 24,
65+
"RO": 11,
66+
"RR": 14,
67+
"RS": 43,
68+
"SC": 42,
69+
"SE": 28,
70+
"SP": 35,
71+
"TO": 17,
72+
"AN": 91,
73+
}
74+
75+
SVRS_STATES = [
76+
"AC",
77+
"AL",
78+
"AM",
79+
"BA",
80+
"CE",
81+
"DF",
82+
"ES",
83+
"GO",
84+
"MA",
85+
"PA",
86+
"PB",
87+
"PI",
88+
"RJ",
89+
"RN",
90+
"RO",
91+
"SC",
92+
"SE",
93+
"TO",
94+
"AP",
95+
"PE",
96+
"RR",
97+
"SP"
98+
]
99+
100+
SVRS = {
61101
AMBIENTE_PRODUCAO: {
62102
"servidor": "mdfe.svrs.rs.gov.br",
63-
WS_MDFE_RECEPCAO: "ws/MDFeRecepcao/MDFeRecepcao.asmx?wsdl",
64103
WS_MDFE_RET_RECEPCAO: "ws/MDFeRetRecepcao/MDFeRetRecepcao.asmx?wsdl",
65104
WS_MDFE_RECEPCAO_EVENTO: "ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx?wsdl",
66105
WS_MDFE_CONSULTA: "ws/MDFeConsulta/MDFeConsulta.asmx?wsdl",
67-
WS_MDFE_SITUACAO: "ws/MDFeStatusServico/MDFeStatusServico.asmx?wsdl",
106+
WS_MDFE_STATUS_SERVICO: "ws/MDFeStatusServico/MDFeStatusServico.asmx?wsdl",
68107
WS_MDFE_CONSULTA_NAO_ENCERRADOS: "ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx?wsdl",
69108
WS_MDFE_DISTRIBUICAO: "ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx?wsdl",
70109
WS_MDFE_RECEPCAO_SINC: "ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx?wsdl",
110+
QR_CODE_URL: "https://dfe-portal.svrs.rs.gov.br/mdfe/qrCode",
71111
},
72112
AMBIENTE_HOMOLOGACAO: {
73113
"servidor": "mdfe-homologacao.svrs.rs.gov.br",
74-
WS_MDFE_RECEPCAO: "ws/MDFeRecepcao/MDFeRecepcao.asmx?wsdl",
75114
WS_MDFE_RET_RECEPCAO: "ws/MDFeRetRecepcao/MDFeRetRecepcao.asmx?wsdl",
76115
WS_MDFE_RECEPCAO_EVENTO: "ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx?wsdl",
77116
WS_MDFE_CONSULTA: "ws/MDFeConsulta/MDFeConsulta.asmx?wsdl",
78-
WS_MDFE_SITUACAO: "ws/MDFeStatusServico/MDFeStatusServico.asmx?wsdl",
117+
WS_MDFE_STATUS_SERVICO: "ws/MDFeStatusServico/MDFeStatusServico.asmx?wsdl",
79118
WS_MDFE_CONSULTA_NAO_ENCERRADOS: "ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx?wsdl",
80119
WS_MDFE_DISTRIBUICAO: "ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx?wsdl",
81120
WS_MDFE_RECEPCAO_SINC: "ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx?wsdl",
121+
QR_CODE_URL: "https://dfe-portal.svrs.rs.gov.br/mdfe/qrCode",
82122
},
83123
}
84124

85-
QR_CODE_URL = "https://dfe-portal.svrs.rs.gov.br/mdfe/qrCode"
86-
87-
NAMESPACES = {
88-
"mdfe": "http://www.portalfiscal.inf.br/mdfe",
89-
"ds": "http://www.w3.org/2000/09/xmldsig#",
90-
}
125+
def get_service_url(sigla_estado, service, ambiente):
126+
if sigla_estado in SVRS_STATES:
127+
state_config = SVRS
128+
else:
129+
state_config = sigla_estado
91130

131+
if not state_config:
132+
raise ValueError(
133+
f"Estado {sigla_estado} não suportado ou configuração ausente."
134+
)
92135

93-
def localizar_url(servico, ambiente=2):
94-
dominio = SVC_RS[ambiente]["servidor"]
95-
complemento = SVC_RS[ambiente][servico]
136+
environment = AMBIENTE_PRODUCAO if ambiente == 1 else AMBIENTE_HOMOLOGACAO
137+
if service == "QRCode":
138+
return state_config[environment][QR_CODE_URL]
96139

97-
return f"https://{dominio}/{complemento}"
140+
server = state_config[environment]["servidor"]
141+
service_path = state_config[environment][service]
142+
return f"https://{server}/{service_path}"
98143

99144

100145
class MDFe(DocumentoEletronico):
@@ -114,123 +159,79 @@ def __init__(self, transmissao, uf, versao="3.00", ambiente="2", mod="58"):
114159
self.uf = int(uf)
115160
self.mod = str(mod)
116161

162+
def _get_ws_endpoint(self, service):
163+
sigla = None
164+
for uf_code, ibge_code in SIGLA_ESTADO.items():
165+
if ibge_code == self.uf:
166+
sigla = uf_code
167+
break
168+
169+
if not sigla:
170+
raise ValueError(f"UF {self.uf} não suportado ou configuração ausente.")
171+
172+
return get_service_url(sigla, service, self.ambiente)
173+
117174
def _verifica_resposta_envio_sucesso(self, proc_envio):
118175
return (
119176
proc_envio.resposta.cStat
120177
== self._edoc_situacao_arquivo_recebido_com_sucesso
121178
)
122179

123-
def _verifica_servico_em_operacao(self, proc_servico):
124-
return proc_servico.resposta.cStat == self._edoc_situacao_servico_em_operacao
125-
126-
def _aguarda_tempo_medio(self, proc_envio):
127-
time.sleep(float(proc_envio.resposta.infRec.tMed) * 1.3)
128-
129-
def _edoc_situacao_em_processamento(self, proc_recibo):
130-
return proc_recibo.resposta.cStat == "105"
180+
def status_servico(self):
181+
raiz = ConsStatServMdfe(tpAmb=self.ambiente, versao=self.versao)
182+
return self._post(
183+
raiz=raiz,
184+
url=self._get_ws_endpoint(WS_MDFE_STATUS_SERVICO),
185+
operacao="mdfeStatusServicoMDF",
186+
classe=RetConsStatServMdfe,
187+
)
131188

132189
def get_documento_id(self, edoc):
133-
return edoc.infMDFe.Id[:3], edoc.infMDFe.Id[3:]
190+
return edoc.infMdfe.Id[:3], edoc.infMdfe.Id[3:]
134191

135192
def monta_qrcode(self, chave):
136-
return f"{QR_CODE_URL}?chMDFe={chave}&tpAmb={self.ambiente}"
137-
138-
def monta_qrcode_contingencia(self, edoc, xml_assinado):
139-
chave = edoc.infMDFe.Id.replace("MDFe", "")
140-
141-
xml = etree.fromstring(xml_assinado)
142-
digest_value = xml.find(".//ds:DigestValue", namespaces=NAMESPACES).text
143-
digest_value_hex = binascii.hexlify(digest_value.encode()).decode()
144-
145-
return f"{self.monta_qrcode(chave)}&sign={digest_value_hex}"
146-
147-
def status_servico(self):
148-
return self._post(
149-
ConsStatServMdfe(tpAmb=self.ambiente, versao=self.versao),
150-
localizar_url(WS_MDFE_SITUACAO, int(self.ambiente)),
151-
"mdfeStatusServicoMDF",
152-
RetConsStatServMdfe,
193+
return (
194+
f"{self._get_ws_endpoint(QR_CODE_URL)}?chMDFe={chave}&tpAmb={self.ambiente}"
153195
)
154196

155197
def consulta_documento(self, chave):
156-
raiz = ConsSitMdfe(
157-
versao=self.versao,
158-
tpAmb=self.ambiente,
159-
chMDFe=chave,
160-
)
198+
raiz = ConsSitMdfe(tpAmb=self.ambiente, chMDFe=chave, versao=self.versao)
161199
return self._post(
162-
raiz,
163-
localizar_url(WS_MDFE_CONSULTA, int(self.ambiente)),
164-
"mdfeConsultaMDF",
165-
RetConsSitMdfe,
166-
)
167-
168-
def consulta_nao_encerrados(self, cnpj):
169-
raiz = ConsMdfeNaoEnc(
170-
versao=self.versao,
171-
tpAmb=self.ambiente,
172-
CNPJ=cnpj,
173-
)
174-
return self._post(
175-
raiz,
176-
localizar_url(WS_MDFE_CONSULTA_NAO_ENCERRADOS, int(self.ambiente)),
177-
"mdfeConsNaoEnc",
178-
RetConsMdfeNaoEnc,
200+
raiz=raiz,
201+
url=self._get_ws_endpoint(WS_MDFE_CONSULTA),
202+
operacao="mdfeConsulta",
203+
classe=RetConsSitMdfe,
179204
)
180205

181206
def envia_documento(self, edoc):
182-
"""
207+
xml_assinado = self.assina_raiz(edoc, edoc.infMDFe.Id)
183208

184-
Exportar o documento
185-
Assinar o documento
186-
Adicionar o mesmo ao envio
209+
# Compactar o XML assinado com GZip
210+
gzipped_xml = gzip.compress(xml_assinado.encode("utf-8"))
211+
212+
# Codificar o XML compactado em Base64
213+
base64_gzipped_xml = base64.b64encode(gzipped_xml).decode("utf-8")
187214

188-
:param edoc:
189-
:return:
190-
"""
191-
raiz = EnviMdfe(
192-
versao=self.versao,
193-
idLote=datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
194-
MDFe=edoc,
195-
)
196-
xml_assinado = self.assina_raiz(raiz, edoc.infMDFe.Id)
197215
return self._post(
198-
xml_assinado,
199-
localizar_url(WS_MDFE_RECEPCAO, int(self.ambiente)),
200-
"mdfeRecepcaoLote",
201-
RetEnviMdfe,
216+
raiz=base64_gzipped_xml,
217+
url=self._get_ws_endpoint(WS_MDFE_RECEPCAO_SINC),
218+
operacao="mdfeRecepcao",
219+
classe=RetMdfe,
202220
)
203221

204-
def consulta_recibo(self, numero=False, proc_envio=False):
205-
if proc_envio:
206-
numero = proc_envio.resposta.infRec.nRec
207-
208-
if not numero:
209-
return
210-
211-
raiz = ConsReciMdfe(
222+
def monta_mdfe_proc(self, doc, prot):
223+
"""
224+
Constrói e retorna o XML do processo da MDF-e,
225+
incorporando a MDF-e com o seu protocolo de autorização.
226+
"""
227+
proc = etree.Element(
228+
f"{{{self._namespace}}}mdfeProc",
212229
versao=self.versao,
213-
tpAmb=self.ambiente,
214-
nRec=numero,
230+
nsmap={None: self._namespace},
215231
)
216-
return self._post(
217-
raiz,
218-
localizar_url(WS_MDFE_RET_RECEPCAO, int(self.ambiente)),
219-
"mdfeRetRecepcao",
220-
RetConsReciMdfe,
221-
)
222-
223-
def monta_processo(self, edoc, proc_envio, proc_recibo):
224-
mdfe = proc_envio.envio_raiz.find("{" + self._namespace + "}MDFe")
225-
protocolos = proc_recibo.resposta.protMDFe
226-
if mdfe and protocolos:
227-
if not isinstance(protocolos, list):
228-
protocolos = [protocolos]
229-
for protocolo in protocolos:
230-
mdfe_proc = MdfeProc(versao=self.versao, protMDFe=protocolo)
231-
proc_recibo.processo = mdfe_proc
232-
proc_recibo.processo_xml = mdfe_proc.to_xml()
233-
proc_recibo.protocolo = protocolo
232+
proc.append(doc)
233+
proc.append(prot)
234+
return etree.tostring(proc)
234235

235236
def envia_evento(self, evento, tipo, chave, sequencia="001", data_hora=False):
236237
inf_evento = EventoMdfe.InfEvento(
@@ -251,7 +252,7 @@ def envia_evento(self, evento, tipo, chave, sequencia="001", data_hora=False):
251252

252253
return self._post(
253254
xml_assinado,
254-
localizar_url(WS_MDFE_RECEPCAO_EVENTO, int(self.ambiente)),
255+
self._get_ws_endpoint(WS_MDFE_RECEPCAO_EVENTO),
255256
"mdfeRecepcaoEvento",
256257
RetEventoMdfe,
257258
)
@@ -279,3 +280,18 @@ def encerra_documento(
279280
return self.envia_evento(
280281
evento=encerramento, tipo="110112", chave=chave, data_hora=data_hora_evento
281282
)
283+
284+
def consulta_recibo(self):
285+
pass
286+
287+
class TransmissaoMDFE(TransmissaoSOAP):
288+
def interpretar_mensagem(self, mensagem, **kwargs):
289+
if isinstance(mensagem, str):
290+
try:
291+
return etree.fromstring(
292+
mensagem, parser=etree.XMLParser(remove_blank_text=True)
293+
)
294+
except (etree.XMLSyntaxError, ValueError):
295+
# Retorna a string original se houver um erro na conversão
296+
return mensagem
297+
return mensagem

0 commit comments

Comments
 (0)