|
16 | 16 | NAMESPACE_SOAP, |
17 | 17 | CODIGOS_ESTADOS, |
18 | 18 | NAMESPACE_BETHA, |
19 | | - NAMESPACE_METODO |
| 19 | + NAMESPACE_METODO, |
| 20 | + NAMESPACE_CTE, |
| 21 | + VERSAO_CTE, |
| 22 | + NAMESPACE_CTE_METODO |
20 | 23 | ) |
21 | | -from pynfe.utils.webservices import NFE, NFCE, NFSE, MDFE |
| 24 | +from pynfe.utils.webservices import NFE, NFCE, NFSE, MDFE, CTE |
22 | 25 | from pynfe.entidades.certificado import CertificadoA1 |
23 | 26 | from .assinatura import AssinaturaA1 |
24 | 27 |
|
@@ -984,3 +987,199 @@ def _get_url(self, consulta): |
984 | 987 |
|
985 | 988 | self.url = ambiente + MDFE['SVRS'][consulta] |
986 | 989 | 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('<', '<').replace('>', '>').replace('&', ''), |
| 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() |
0 commit comments