Skip to content

Commit ca7dff2

Browse files
authored
Merge pull request #207 from leogregianin/feat/cte
Adiciona classe inicial para webservice de CTe
2 parents 481dada + 20cdd87 commit ca7dff2

5 files changed

Lines changed: 264 additions & 3 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ Características
5151
* Evento de Inclusão de DF-e
5252
* Evento de Pagamento DF-e
5353

54+
* CT-e:
55+
* Atualizado para a versão 3.00
56+
* Consultar Distribuição DF-e para CT-e
57+
* Emissão (A fazer)
58+
* Inutilização (A fazer)
59+
* Consultar CT-e pela chave de acesso (A fazer)
60+
* Consultar Status do Serviço (A fazer)
61+
* Eventos relacionados a CT-e (A fazer)
62+
5463
Dependências
5564
------------
5665

pynfe/processamento/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .comunicacao import (
66
ComunicacaoSefaz,
77
ComunicacaoNfse,
8-
ComunicacaoMDFe
8+
ComunicacaoMDFe,
9+
ComunicacaoCTe,
910
)
1011
from .danfe import DanfeNfce

pynfe/processamento/comunicacao.py

Lines changed: 201 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
NAMESPACE_SOAP,
1717
CODIGOS_ESTADOS,
1818
NAMESPACE_BETHA,
19-
NAMESPACE_METODO
19+
NAMESPACE_METODO,
20+
NAMESPACE_CTE,
21+
VERSAO_CTE,
22+
NAMESPACE_CTE_METODO
2023
)
21-
from pynfe.utils.webservices import NFE, NFCE, NFSE, MDFE
24+
from pynfe.utils.webservices import NFE, NFCE, NFSE, MDFE, CTE
2225
from pynfe.entidades.certificado import CertificadoA1
2326
from .assinatura import AssinaturaA1
2427

@@ -984,3 +987,199 @@ def _get_url(self, consulta):
984987

985988
self.url = ambiente + MDFE['SVRS'][consulta]
986989
return self.url
990+
991+
992+
class ComunicacaoCTe(Comunicacao):
993+
"""Classe de comunicação que segue o padrão definido para as SEFAZ dos Estados."""
994+
995+
_versao = VERSAO_CTE
996+
_assinatura = AssinaturaA1
997+
_namespace = NAMESPACE_CTE
998+
_header = 'cteCabecMsg'
999+
_envio_mensagem = 'cteDadosMsg'
1000+
_namespace_metodo = NAMESPACE_CTE_METODO
1001+
_accept = False
1002+
_namespace_soap = NAMESPACE_SOAP
1003+
_namespace_xsi = NAMESPACE_XSI
1004+
_namespace_xsd = NAMESPACE_XSD
1005+
_soap_version = 'soap'
1006+
1007+
def status_servico(self):
1008+
"""
1009+
Verifica status do servidor da receita.
1010+
:return:
1011+
"""
1012+
url = self._get_url('STATUS')
1013+
# Monta XML do corpo da requisição
1014+
raiz = etree.Element('consStatServ', versao=self._versao, xmlns=NAMESPACE_CTE)
1015+
etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente)
1016+
etree.SubElement(raiz, 'cUF').text = CODIGOS_ESTADOS[self.uf.upper()]
1017+
etree.SubElement(raiz, 'xServ').text = 'STATUS'
1018+
xml = self._construir_xml_soap('CteStatusServico', raiz)
1019+
return self._post(url, xml)
1020+
1021+
def consulta_distribuicao(self, cnpj=None, cpf=None, chave=None, nsu=0):
1022+
"""
1023+
O XML do pedido de distribuição suporta três tipos de consultas que são
1024+
definidas de acordo com a tag informada no XML.
1025+
As tags são distNSU, consNSU e consChCTe.
1026+
a) distNSU – Distribuição de Conjunto de DF-e a Partir do NSU Informado
1027+
b) consNSU – Consulta DF-e Vinculado ao NSU Informado
1028+
c) consChCTe – Consulta de NF-e por Chave de Acesso Informada
1029+
:param cnpj: CNPJ do interessado
1030+
:param cpf: CPF do interessado
1031+
:param chave: Chave do CT-e a ser consultada
1032+
:param nsu: Ultimo nsu ou nsu específico para ser consultado.
1033+
:return:
1034+
"""
1035+
# url
1036+
url = self._get_url_an(consulta='DISTRIBUICAO')
1037+
# Monta XML para envio da requisição
1038+
raiz = etree.Element('distDFeInt', versao='1.00', xmlns=NAMESPACE_CTE)
1039+
etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente)
1040+
if self.uf:
1041+
etree.SubElement(raiz, 'cUFAutor').text = CODIGOS_ESTADOS[self.uf.upper()]
1042+
if cnpj:
1043+
etree.SubElement(raiz, 'CNPJ').text = cnpj
1044+
else:
1045+
etree.SubElement(raiz, 'CPF').text = cpf
1046+
if not chave:
1047+
distNSU = etree.SubElement(raiz, 'distNSU')
1048+
etree.SubElement(distNSU, 'ultNSU').text = str(nsu).zfill(15)
1049+
if chave:
1050+
consChCTe = etree.SubElement(raiz, 'consChCTe')
1051+
etree.SubElement(consChCTe, 'chCTe').text = chave
1052+
#Monta XML para envio da requisição
1053+
xml = self._construir_xml_soap('CTeDistribuicaoDFe', raiz)
1054+
return self._post(url, xml)
1055+
1056+
def _get_url_an(self, consulta):
1057+
# producao
1058+
if self._ambiente == 1:
1059+
if consulta == 'DISTRIBUICAO':
1060+
ambiente = 'https://www1.'
1061+
else:
1062+
ambiente = 'https://www.'
1063+
# homologacao
1064+
else:
1065+
ambiente = 'https://hom.'
1066+
1067+
self.url = ambiente + CTE['AN'][consulta]
1068+
return self.url
1069+
1070+
def _cabecalho_soap(self, metodo):
1071+
"""Monta o XML do cabeçalho da requisição SOAP"""
1072+
1073+
raiz = etree.Element(
1074+
self._header,
1075+
xmlns=self._namespace_metodo + metodo
1076+
)
1077+
etree.SubElement(raiz, 'cUF').text = CODIGOS_ESTADOS[self.uf.upper()]
1078+
etree.SubElement(raiz, 'versaoDados').text = '3.00'
1079+
return raiz
1080+
1081+
def _get_url(self, consulta):
1082+
""" Retorna a url para comunicação com o webservice """
1083+
1084+
# Estados que implementam webservices proprios
1085+
lista = ['MT', 'MS', 'MG', 'PR', 'RS', 'SP']
1086+
if self.uf.upper() in lista:
1087+
if self._ambiente == 1:
1088+
ambiente = 'HTTPS'
1089+
else:
1090+
ambiente = 'HOMOLOGACAO'
1091+
self.url = CTE[self.uf.upper()][ambiente] + CTE[self.uf.upper()][consulta]
1092+
1093+
# Estados que utilizam outros ambientes
1094+
else:
1095+
lista_svrs = [
1096+
'AC', 'AL', 'AM', 'BA', 'CE',
1097+
'DF', 'ES', 'GO', 'MA', 'PA',
1098+
'PB', 'PI', 'RJ', 'RN', 'RO',
1099+
'SC', 'SE', 'TO'
1100+
]
1101+
lista_svsp = ['AP', 'PE', 'RR']
1102+
1103+
# SVRS
1104+
if self.uf.upper() in lista_svrs:
1105+
if self._ambiente == 1:
1106+
ambiente = 'HTTPS'
1107+
else:
1108+
ambiente = 'HOMOLOGACAO'
1109+
self.url = CTE['SVRS'][ambiente] + CTE['SVRS'][consulta]
1110+
# SVSP
1111+
elif self.uf.upper() in lista_svsp:
1112+
if self._ambiente == 1:
1113+
ambiente = 'HTTPS'
1114+
else:
1115+
ambiente = 'HOMOLOGACAO'
1116+
self.url = CTE['SVSP'][ambiente] + CTE['SVSP'][consulta]
1117+
else:
1118+
raise Exception(f"Url não encontrada para {consulta} {self.uf.upper()}")
1119+
return self.url
1120+
1121+
def _construir_xml_soap(self, metodo, dados, cabecalho=False):
1122+
"""Monta o XML para o envio via SOAP"""
1123+
1124+
raiz = etree.Element(
1125+
'{%s}Envelope' % NAMESPACE_SOAP,
1126+
nsmap={
1127+
'xsi': NAMESPACE_XSI,
1128+
'xsd': NAMESPACE_XSD,
1129+
'soap': NAMESPACE_SOAP
1130+
}
1131+
)
1132+
1133+
if self._header:
1134+
cabecalho = self._cabecalho_soap(metodo)
1135+
c = etree.SubElement(raiz, '{%s}Header' % self._namespace_soap)
1136+
c.append(cabecalho)
1137+
1138+
body = etree.SubElement(raiz, '{%s}Body' % NAMESPACE_SOAP)
1139+
# distribuição tem um corpo de xml diferente
1140+
if metodo == 'CTeDistribuicaoDFe':
1141+
x = etree.SubElement(body, 'cteDistDFeInteresse', xmlns=NAMESPACE_CTE_METODO+metodo)
1142+
a = etree.SubElement(x, 'cteDadosMsg')
1143+
else:
1144+
a = etree.SubElement(body, 'cteDadosMsg', xmlns=NAMESPACE_CTE_METODO+metodo)
1145+
a.append(dados)
1146+
return raiz
1147+
1148+
def _post_header(self):
1149+
"""Retorna um dicionário com os atributos para o cabeçalho da requisição HTTP"""
1150+
response = {
1151+
'content-type': 'application/soap+xml; charset=utf-8;',
1152+
'Accept': 'application/soap+xml; charset=utf-8;',
1153+
}
1154+
response["SOAPAction"] = ""
1155+
return response
1156+
1157+
def _post(self, url, xml):
1158+
certificado_a1 = CertificadoA1(self.certificado)
1159+
chave, cert = certificado_a1.separar_arquivo(self.certificado_senha, caminho=True)
1160+
chave_cert = (cert, chave)
1161+
# Abre a conexão HTTPS
1162+
try:
1163+
xml_declaration = '<?xml version="1.0" encoding="UTF-8"?>'
1164+
1165+
# limpa xml com caracteres bugados para infNFeSupl em NFC-e
1166+
xml = re.sub(
1167+
'<qrCode>(.*?)</qrCode>',
1168+
lambda x: x.group(0).replace('&lt;', '<').replace('&gt;', '>').replace('&amp;', ''),
1169+
etree.tostring(xml, encoding='unicode').replace('\n', '')
1170+
)
1171+
xml = xml_declaration + xml
1172+
1173+
print(xml)
1174+
print('-' * 20)
1175+
1176+
# Faz o request com o servidor
1177+
result = requests.post(url, xml, headers=self._post_header(), cert=chave_cert, verify=False, timeout=300)
1178+
result.encoding = 'utf-8'
1179+
return result
1180+
except requests.exceptions.Timeout as e:
1181+
raise e
1182+
except requests.exceptions.RequestException as e:
1183+
raise e
1184+
finally:
1185+
certificado_a1.excluir()

pynfe/utils/flags.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
VERSAO_PADRAO = '4.00'
1919

20+
NAMESPACE_CTE = 'http://www.portalfiscal.inf.br/cte'
21+
NAMESPACE_CTE_METODO = 'http://www.portalfiscal.inf.br/cte/wsdl/'
22+
VERSAO_CTE = '3.00'
23+
2024
VERSAO_QRCODE = '2'
2125

2226
TIPOS_DOCUMENTO = (

pynfe/utils/webservices.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,3 +509,51 @@
509509
'QRCODE': 'https://dfe-portal.svrs.rs.gov.br/mdfe/qrCode'
510510
}
511511
}
512+
513+
CTE = {
514+
'AN': {
515+
'DISTRIBUICAO': 'cte.fazenda.gov.br/CTeDistribuicaoDFe/CTeDistribuicaoDFe.asmx',
516+
'HTTPS': 'https://www1',
517+
'HOMOLOGACAO': 'https://hom1'
518+
},
519+
'MT': {
520+
'STATUS': 'sefaz.mt.gov.br/ctews/services/CteStatusServico',
521+
'HTTPS': 'https://cte.',
522+
'HOMOLOGACAO': 'https://homologacao.'
523+
},
524+
'MS': {
525+
'STATUS': 'cte.ms.gov.br/ws/CteStatusServico',
526+
'HTTPS': 'https://producao.',
527+
'HOMOLOGACAO': 'https://homologacao.'
528+
},
529+
'MG': {
530+
'STATUS': 'fazenda.mg.gov.br/cte/services/CteStatusServico',
531+
'HTTPS': 'https://cte.',
532+
'HOMOLOGACAO': 'https://hcte.'
533+
},
534+
'PR': {
535+
'STATUS': 'fazenda.pr.gov.br/cte/CteStatusServico?wsdl',
536+
'HTTPS': 'https://cte.',
537+
'HOMOLOGACAO': 'https://homologacao.'
538+
},
539+
'RS': {
540+
'STATUS': 'svrs.rs.gov.br/ws/ctestatusservico/CteStatusServico.asmx',
541+
'HTTPS': 'https://cte.',
542+
'HOMOLOGACAO': 'https://cte-homologacao.'
543+
},
544+
'SP': {
545+
'STATUS': 'fazenda.sp.gov.br/cteWEB/services/cteStatusServico.asmx',
546+
'HTTPS': 'https://nfe.',
547+
'HOMOLOGACAO': 'https://homologacao.nfe.'
548+
},
549+
'SVRS': {
550+
'STATUS': 'svrs.rs.gov.br/ws/ctestatusservico/CteStatusServico.asmx',
551+
'HTTPS': 'https://cte.',
552+
'HOMOLOGACAO': 'https://cte-homologacao.'
553+
},
554+
'SVSP': {
555+
'STATUS': 'fazenda.sp.gov.br/cteWEB/services/CteStatusServico.asmx',
556+
'HTTPS': 'https://nfe.',
557+
'HOMOLOGACAO': 'https://homologacao.nfe.'
558+
},
559+
}

0 commit comments

Comments
 (0)