1
1
# Copyright (C) 2019 Luis Felipe Mileo - KMEE
2
2
3
3
4
- import binascii
5
- import datetime
6
- import time
4
+ import base64
5
+ import gzip
6
+ from contextlib import suppress
7
7
8
8
from lxml import etree
9
9
10
10
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 ,
38
22
RetConsStatServMdfe ,
23
+ RetMdfe ,
24
+ RetEventoMdfe ,
39
25
)
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
44
29
45
30
WS_MDFE_CONSULTA = "MDFeConsulta"
46
- WS_MDFE_SITUACAO = "MDFeStatusServico "
31
+ WS_MDFE_STATUS_SERVICO = "MDFeStatusServicoMDF "
47
32
WS_MDFE_CONSULTA_NAO_ENCERRADOS = "MDFeConsNaoEnc"
48
33
WS_MDFE_DISTRIBUICAO = "MDFeDistribuicaoDFe"
49
34
52
37
WS_MDFE_RET_RECEPCAO = "MDFeRetRecepcao"
53
38
WS_MDFE_RECEPCAO_EVENTO = "MDFeRecepcaoEvento"
54
39
55
- AMBIENTE_PRODUCAO = 1
56
- AMBIENTE_HOMOLOGACAO = 2
40
+ QR_CODE_URL = "QRCode"
57
41
58
42
MDFE_MODELO = "58"
59
43
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 = {
61
101
AMBIENTE_PRODUCAO : {
62
102
"servidor" : "mdfe.svrs.rs.gov.br" ,
63
- WS_MDFE_RECEPCAO : "ws/MDFeRecepcao/MDFeRecepcao.asmx?wsdl" ,
64
103
WS_MDFE_RET_RECEPCAO : "ws/MDFeRetRecepcao/MDFeRetRecepcao.asmx?wsdl" ,
65
104
WS_MDFE_RECEPCAO_EVENTO : "ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx?wsdl" ,
66
105
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" ,
68
107
WS_MDFE_CONSULTA_NAO_ENCERRADOS : "ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx?wsdl" ,
69
108
WS_MDFE_DISTRIBUICAO : "ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx?wsdl" ,
70
109
WS_MDFE_RECEPCAO_SINC : "ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx?wsdl" ,
110
+ QR_CODE_URL : "https://dfe-portal.svrs.rs.gov.br/mdfe/qrCode" ,
71
111
},
72
112
AMBIENTE_HOMOLOGACAO : {
73
113
"servidor" : "mdfe-homologacao.svrs.rs.gov.br" ,
74
- WS_MDFE_RECEPCAO : "ws/MDFeRecepcao/MDFeRecepcao.asmx?wsdl" ,
75
114
WS_MDFE_RET_RECEPCAO : "ws/MDFeRetRecepcao/MDFeRetRecepcao.asmx?wsdl" ,
76
115
WS_MDFE_RECEPCAO_EVENTO : "ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx?wsdl" ,
77
116
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" ,
79
118
WS_MDFE_CONSULTA_NAO_ENCERRADOS : "ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx?wsdl" ,
80
119
WS_MDFE_DISTRIBUICAO : "ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx?wsdl" ,
81
120
WS_MDFE_RECEPCAO_SINC : "ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx?wsdl" ,
121
+ QR_CODE_URL : "https://dfe-portal.svrs.rs.gov.br/mdfe/qrCode" ,
82
122
},
83
123
}
84
124
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
91
130
131
+ if not state_config :
132
+ raise ValueError (
133
+ f"Estado { sigla_estado } não suportado ou configuração ausente."
134
+ )
92
135
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 ]
96
139
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 } "
98
143
99
144
100
145
class MDFe (DocumentoEletronico ):
@@ -114,123 +159,79 @@ def __init__(self, transmissao, uf, versao="3.00", ambiente="2", mod="58"):
114
159
self .uf = int (uf )
115
160
self .mod = str (mod )
116
161
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
+
117
174
def _verifica_resposta_envio_sucesso (self , proc_envio ):
118
175
return (
119
176
proc_envio .resposta .cStat
120
177
== self ._edoc_situacao_arquivo_recebido_com_sucesso
121
178
)
122
179
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
+ )
131
188
132
189
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 :]
134
191
135
192
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 } "
153
195
)
154
196
155
197
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 )
161
199
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 ,
179
204
)
180
205
181
206
def envia_documento (self , edoc ):
182
- """
207
+ xml_assinado = self . assina_raiz ( edoc , edoc . infMDFe . Id )
183
208
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" )
187
214
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 )
197
215
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 ,
202
220
)
203
221
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" ,
212
229
versao = self .versao ,
213
- tpAmb = self .ambiente ,
214
- nRec = numero ,
230
+ nsmap = {None : self ._namespace },
215
231
)
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 )
234
235
235
236
def envia_evento (self , evento , tipo , chave , sequencia = "001" , data_hora = False ):
236
237
inf_evento = EventoMdfe .InfEvento (
@@ -251,7 +252,7 @@ def envia_evento(self, evento, tipo, chave, sequencia="001", data_hora=False):
251
252
252
253
return self ._post (
253
254
xml_assinado ,
254
- localizar_url ( WS_MDFE_RECEPCAO_EVENTO , int ( self .ambiente ) ),
255
+ self ._get_ws_endpoint ( WS_MDFE_RECEPCAO_EVENTO ),
255
256
"mdfeRecepcaoEvento" ,
256
257
RetEventoMdfe ,
257
258
)
@@ -279,3 +280,18 @@ def encerra_documento(
279
280
return self .envia_evento (
280
281
evento = encerramento , tipo = "110112" , chave = chave , data_hora = data_hora_evento
281
282
)
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