Skip to content

Commit 7ae1baf

Browse files
committed
cte: distribuiçao de CTe
1 parent 3903547 commit 7ae1baf

7 files changed

Lines changed: 409 additions & 9 deletions

File tree

.xsdata.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
<PostponedAnnotations>false</PostponedAnnotations>
1111
<UnnestClasses>false</UnnestClasses>
1212
</Output>
13-
<Extensions>
13+
<!--Extensions>
1414
<Extension type="class" class=".*" import="nfelib.CommonMixin" prepend="false" applyIfDerived="false"/>
15-
</Extensions>
15+
</Extensions-->
1616
<Conventions>
1717
<ClassName case="pascalCase" safePrefix="type"/>
1818
<FieldName case="originalCase" safePrefix="value"/>

nfelib/cte/client/v4_0/dfe.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Copyright (C) 2026 Raphaël Valyi - Akretion
2+
3+
import logging
4+
from typing import Any, Optional
5+
6+
from brazil_fiscal_client.fiscal_client import (
7+
FiscalClient,
8+
Tamb,
9+
)
10+
11+
# --- Content Bindings ---
12+
from nfelib.cte.client.v4_0.servers import Endpoint
13+
14+
# --- Server Definitions ---
15+
from nfelib.cte.client.v4_0.servers import servers as SERVERS_CTE
16+
17+
# --- SOAP Bindings ---
18+
from nfelib.cte.soap.v4_0.ctedistribuicaodfe import (
19+
CteDistribuicaoDfeSoapCteDistDfeInteresse,
20+
)
21+
22+
# --- Dist DF-e ---
23+
from nfelib.cte_dist_dfe.bindings.v1_0 import DistDfeInt, RetDistDfeInt
24+
25+
_logger = logging.getLogger(__name__)
26+
27+
28+
class DfeClient(FiscalClient):
29+
"""A façade for the CTe SOAP webservices."""
30+
31+
def __init__(self, **kwargs: Any):
32+
self.mod = kwargs.pop("mod", "55")
33+
super().__init__(
34+
service="nfe",
35+
versao="1.01",
36+
**kwargs,
37+
)
38+
39+
def _get_location(self, endpoint_type: Endpoint) -> str:
40+
"""Construct the full HTTPS URL for the specified service."""
41+
server_key = "AN"
42+
try:
43+
server_data = SERVERS_CTE[server_key]
44+
except KeyError:
45+
raise ValueError(
46+
f"No server configuration found for key: {server_key} "
47+
"(derived from UF {self.uf})"
48+
)
49+
50+
if self.ambiente == Tamb.PROD.value:
51+
endpoints = server_data["prod_endpoints"]
52+
else:
53+
endpoints = server_data.get("dev_endpoints", server_data["prod_endpoints"])
54+
55+
try:
56+
return endpoints[endpoint_type]
57+
except KeyError:
58+
raise ValueError(
59+
f"Endpoint {endpoint_type.name} not configured for server key: "
60+
"{server_key}"
61+
)
62+
63+
def send(self, action_class, obj, **kwargs):
64+
action_to_endpoint_map = {
65+
CteDistribuicaoDfeSoapCteDistDfeInteresse: Endpoint.CTEDISTRIBUICAODFE,
66+
}
67+
endpoint_type = action_to_endpoint_map[action_class]
68+
location = self._get_location(endpoint_type)
69+
70+
wrapped_obj: dict[str, Any]
71+
if isinstance(obj, DistDfeInt):
72+
wrapped_obj = {
73+
# Notice the 'cteDistDFeInteresse' and 'cteDadosMsg' vs CTe
74+
"Body": {"cteDistDFeInteresse": {"cteDadosMsg": {"content": [obj]}}}
75+
}
76+
else:
77+
wrapped_obj = {"Body": {"cteDadosMsg": {"content": [obj]}}}
78+
79+
response = super().send(
80+
action_class,
81+
location,
82+
wrapped_obj,
83+
**kwargs,
84+
)
85+
86+
# Unwrapping response logic
87+
soap_envelope = response.resposta if self.wrap_response else response
88+
result_container = (
89+
soap_envelope.body.cteDistDFeInteresseResponse.cteDistDFeInteresseResult
90+
)
91+
92+
result_container = (
93+
soap_envelope.body.cteDistDFeInteresseResponse.cteDistDFeInteresseResult
94+
)
95+
96+
if not self.wrap_response:
97+
return result_container.content[0]
98+
99+
response.resposta = result_container.content[0]
100+
101+
return response
102+
103+
def consultar_distribuicao(
104+
self,
105+
cnpj_cpf: str,
106+
ultimo_nsu: str = "",
107+
nsu_especifico: str = "",
108+
chave: str = "",
109+
) -> Optional[RetDistDfeInt]:
110+
"""Consultar Distribução de CTe.
111+
112+
:param cnpj_cpf: CPF ou CNPJ a ser consultado
113+
:param ultimo_nsu: Último NSU para pesquisa. Formato: '999999999999999'
114+
:param nsu_especifico: NSU Específico para pesquisa.
115+
Formato: '999999999999999'
116+
:param chave: Chave de acesso do documento
117+
:return: Retorna uma estrutura contendo as estruturas de envio
118+
e retorno preenchidas
119+
"""
120+
if not ultimo_nsu and not nsu_especifico and not chave:
121+
return None
122+
123+
distNSU = consNSU = None
124+
if ultimo_nsu:
125+
distNSU = DistDfeInt.DistNsu(ultNSU=ultimo_nsu)
126+
if nsu_especifico:
127+
consNSU = DistDfeInt.ConsNsu(NSU=nsu_especifico)
128+
129+
if distNSU and consNSU:
130+
# TODO: Raise?
131+
return None
132+
133+
return self.send(
134+
CteDistribuicaoDfeSoapCteDistDfeInteresse,
135+
DistDfeInt(
136+
versao=self.versao,
137+
tpAmb=self.ambiente,
138+
cUFAutor=self.uf,
139+
CNPJ=cnpj_cpf if len(cnpj_cpf) > 11 else None,
140+
CPF=cnpj_cpf if len(cnpj_cpf) <= 11 else None,
141+
distNSU=distNSU,
142+
consNSU=consNSU,
143+
),
144+
)

nfelib/cte/client/v4_0/servers.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Endpoint(Enum):
1717
CTERECEPCAOSINCV4 = "CTeRecepcaoSincV4"
1818
CTERECEPCAOGTVEV4 = "CTeRecepcaoGTVeV4"
1919
CTERECEPCAOSIMPV4 = "CTeRecepcaoSimpV4"
20+
CTEDISTRIBUICAODFE = "CTeDistribuicaoDFe"
2021

2122

2223
class ServerConfig(TypedDict):
@@ -211,8 +212,12 @@ class ServerConfig(TypedDict):
211212
},
212213
},
213214
"AN": {
214-
"soap_version": "1.1",
215-
"prod_endpoints": {},
216-
"dev_endpoints": {},
215+
"soap_version": "1.2",
216+
"prod_endpoints": {
217+
Endpoint.CTEDISTRIBUICAODFE: "https://www1.cte.fazenda.gov.br/CTeDistribuicaoDFe/CTeDistribuicaoDFe.asmx",
218+
},
219+
"dev_endpoints": {
220+
Endpoint.CTEDISTRIBUICAODFE: "https://hom1.cte.fazenda.gov.br/CTeDistribuicaoDFe/CTeDistribuicaoDFe.asmx",
221+
},
217222
},
218223
}

nfelib/cte/client/v4_0/servers_scraper.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from pathlib import Path
55

6-
from nfelib.utils.servers_scraper import fetch_servers, save_servers
6+
from nfelib.utils.servers_scraper import fetch_servers
77
from nfelib.utils.soap_generator import generate_soap
88

99
# Constants
@@ -30,8 +30,9 @@ def main():
3030

3131
servers, endpoints = fetch_servers(PROD_URL, DEV_URL)
3232
if servers:
33-
save_servers(servers, endpoints, OUTPUT_FILE)
34-
generate_soap(servers, endpoints, download, generate)
33+
# FIXME: temporary hack
34+
# save_servers(servers, endpoints, OUTPUT_FILE)
35+
generate_soap(servers, endpoints, download, True) # generate)
3536

3637

3738
if __name__ == "__main__":
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
"""This file was generated by xsdata, v25.7, on 2026-03-04 17:00:38
2+
3+
Generator: DataclassGenerator
4+
See: https://xsdata.readthedocs.io/
5+
"""
6+
7+
from dataclasses import dataclass, field
8+
from typing import Optional
9+
10+
__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte/wsdl/CTeDistribuicaoDFe"
11+
12+
13+
@dataclass
14+
class CteDistDfeInteresse:
15+
class Meta:
16+
name = "cteDistDFeInteresse"
17+
namespace = (
18+
"http://www.portalfiscal.inf.br/cte/wsdl/CTeDistribuicaoDFe"
19+
)
20+
21+
cteDadosMsg: Optional["CteDistDfeInteresse.CteDadosMsg"] = field(
22+
default=None,
23+
metadata={
24+
"type": "Element",
25+
},
26+
)
27+
28+
@dataclass
29+
class CteDadosMsg:
30+
content: list[object] = field(
31+
default_factory=list,
32+
metadata={
33+
"type": "Wildcard",
34+
"namespace": "##any",
35+
"mixed": True,
36+
},
37+
)
38+
39+
40+
@dataclass
41+
class CteDistDfeInteresseResponse:
42+
class Meta:
43+
name = "cteDistDFeInteresseResponse"
44+
namespace = (
45+
"http://www.portalfiscal.inf.br/cte/wsdl/CTeDistribuicaoDFe"
46+
)
47+
48+
cteDistDFeInteresseResult: Optional[
49+
"CteDistDfeInteresseResponse.CteDistDfeInteresseResult"
50+
] = field(
51+
default=None,
52+
metadata={
53+
"type": "Element",
54+
},
55+
)
56+
57+
@dataclass
58+
class CteDistDfeInteresseResult:
59+
content: list[object] = field(
60+
default_factory=list,
61+
metadata={
62+
"type": "Wildcard",
63+
"namespace": "##any",
64+
"mixed": True,
65+
},
66+
)
67+
68+
69+
@dataclass
70+
class CteDistribuicaoDfeSoapCteDistDfeInteresseInput:
71+
class Meta:
72+
name = "Envelope"
73+
namespace = "http://schemas.xmlsoap.org/soap/envelope/"
74+
75+
body: Optional["CteDistribuicaoDfeSoapCteDistDfeInteresseInput.Body"] = (
76+
field(
77+
default=None,
78+
metadata={
79+
"name": "Body",
80+
"type": "Element",
81+
},
82+
)
83+
)
84+
85+
@dataclass
86+
class Body:
87+
cteDistDFeInteresse: Optional[CteDistDfeInteresse] = field(
88+
default=None,
89+
metadata={
90+
"type": "Element",
91+
"namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeDistribuicaoDFe",
92+
},
93+
)
94+
95+
96+
@dataclass
97+
class CteDistribuicaoDfeSoapCteDistDfeInteresseOutput:
98+
class Meta:
99+
name = "Envelope"
100+
namespace = "http://schemas.xmlsoap.org/soap/envelope/"
101+
102+
body: Optional["CteDistribuicaoDfeSoapCteDistDfeInteresseOutput.Body"] = (
103+
field(
104+
default=None,
105+
metadata={
106+
"name": "Body",
107+
"type": "Element",
108+
},
109+
)
110+
)
111+
112+
@dataclass
113+
class Body:
114+
cteDistDFeInteresseResponse: Optional[CteDistDfeInteresseResponse] = (
115+
field(
116+
default=None,
117+
metadata={
118+
"type": "Element",
119+
"namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeDistribuicaoDFe",
120+
},
121+
)
122+
)
123+
fault: Optional[
124+
"CteDistribuicaoDfeSoapCteDistDfeInteresseOutput.Body.Fault"
125+
] = field(
126+
default=None,
127+
metadata={
128+
"name": "Fault",
129+
"type": "Element",
130+
},
131+
)
132+
133+
@dataclass
134+
class Fault:
135+
faultcode: Optional[str] = field(
136+
default=None,
137+
metadata={
138+
"type": "Element",
139+
"namespace": "",
140+
},
141+
)
142+
faultstring: Optional[str] = field(
143+
default=None,
144+
metadata={
145+
"type": "Element",
146+
"namespace": "",
147+
},
148+
)
149+
faultactor: Optional[str] = field(
150+
default=None,
151+
metadata={
152+
"type": "Element",
153+
"namespace": "",
154+
},
155+
)
156+
detail: Optional[str] = field(
157+
default=None,
158+
metadata={
159+
"type": "Element",
160+
"namespace": "",
161+
},
162+
)
163+
164+
165+
class CteDistribuicaoDfeSoapCteDistDfeInteresse:
166+
style = "document"
167+
location = "https://www1.cte.fazenda.gov.br/CTeDistribuicaoDFe/CTeDistribuicaoDFe.asmx"
168+
transport = "http://schemas.xmlsoap.org/soap/http"
169+
soapAction = "http://www.portalfiscal.inf.br/cte/wsdl/CTeDistribuicaoDFe/cteDistDFeInteresse"
170+
input = CteDistribuicaoDfeSoapCteDistDfeInteresseInput
171+
output = CteDistribuicaoDfeSoapCteDistDfeInteresseOutput

0 commit comments

Comments
 (0)