From 9a958fe5bd59306ea684f3ca9b43a4e775167710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 00:44:55 +0000 Subject: [PATCH 01/38] initial NFe servers scraper/scraped URLs --- nfelib/nfe/client/__init__.py | 0 nfelib/nfe/client/servers.py | 161 +++++++++++++++++++++++++++ nfelib/nfe/client/servers_scraper.py | 98 ++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 nfelib/nfe/client/__init__.py create mode 100644 nfelib/nfe/client/servers.py create mode 100644 nfelib/nfe/client/servers_scraper.py diff --git a/nfelib/nfe/client/__init__.py b/nfelib/nfe/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nfelib/nfe/client/servers.py b/nfelib/nfe/client/servers.py new file mode 100644 index 00000000..7c50005d --- /dev/null +++ b/nfelib/nfe/client/servers.py @@ -0,0 +1,161 @@ +# Auto-generated file. Do not edit manually. + +servers = { + 'AM': { + 'prod_server': 'homnfe.sefaz.am.gov.br', + 'dev_server': 'homnfe.sefaz.am.gov.br', + 'NfeInutilizacao': '/services2/services/NfeInutilizacao4', + 'NfeConsultaProtocolo': '/services2/services/NfeConsulta4', + 'NfeStatusServico': '/services2/services/NfeStatusServico4', + 'NfeConsultaCadastro': '/services2/services/CadConsultaCadastro4', + 'RecepcaoEvento': '/services2/services/RecepcaoEvento4', + 'NFeAutorizacao': '/services2/services/NfeAutorizacao4', + 'NFeRetAutorizacao': '/services2/services/NfeRetAutorizacao4', + }, + 'BA': { + 'prod_server': 'hnfe.sefaz.ba.gov.br', + 'dev_server': 'hnfe.sefaz.ba.gov.br', + 'NfeInutilizacao': '/webservices/NFeInutilizacao4/NFeInutilizacao4.asmx', + 'NfeConsultaProtocolo': '/webservices/NFeConsultaProtocolo4/NFeConsultaProtocolo4.asmx', + 'NfeStatusServico': '/webservices/NFeStatusServico4/NFeStatusServico4.asmx', + 'NfeConsultaCadastro': '/webservices/CadConsultaCadastro4/CadConsultaCadastro4.asmx', + 'RecepcaoEvento': '/webservices/NFeRecepcaoEvento4/NFeRecepcaoEvento4.asmx', + 'NFeAutorizacao': '/webservices/NFeAutorizacao4/NFeAutorizacao4.asmx', + 'NFeRetAutorizacao': '/webservices/NFeRetAutorizacao4/NFeRetAutorizacao4.asmx', + }, + 'GO': { + 'prod_server': 'homolog.sefaz.go.gov.br', + 'dev_server': 'homolog.sefaz.go.gov.br', + 'NfeInutilizacao': '/nfe/services/NFeInutilizacao4?wsdl', + 'NfeConsultaProtocolo': '/nfe/services/NFeConsultaProtocolo4?wsdl', + 'NfeStatusServico': '/nfe/services/NFeStatusServico4?wsdl', + 'NfeConsultaCadastro': '/nfe/services/CadConsultaCadastro4?wsdl', + 'RecepcaoEvento': '/nfe/services/NFeRecepcaoEvento4?wsdl', + 'NFeAutorizacao': '/nfe/services/NFeAutorizacao4?wsdl', + 'NFeRetAutorizacao': '/nfe/services/NFeRetAutorizacao4?wsdl', + }, + 'MG': { + 'prod_server': 'hnfe.fazenda.mg.gov.br', + 'dev_server': 'hnfe.fazenda.mg.gov.br', + 'NfeInutilizacao': '/nfe2/services/NFeInutilizacao4', + 'NfeConsultaProtocolo': '/nfe2/services/NFeConsultaProtocolo4', + 'NfeStatusServico': '/nfe2/services/NFeStatusServico4', + 'NfeConsultaCadastro': '/nfe2/services/CadConsultaCadastro4', + 'RecepcaoEvento': '/nfe2/services/NFeRecepcaoEvento4', + 'NFeAutorizacao': '/nfe2/services/NFeAutorizacao4', + 'NFeRetAutorizacao': '/nfe2/services/NFeRetAutorizacao4', + }, + 'MS': { + 'prod_server': 'hom.nfe.sefaz.ms.gov.br', + 'dev_server': 'hom.nfe.sefaz.ms.gov.br', + 'NfeInutilizacao': '/ws/NFeInutilizacao4', + 'NfeConsultaProtocolo': '/ws/NFeConsultaProtocolo4', + 'NfeStatusServico': '/ws/NFeStatusServico4', + 'NfeConsultaCadastro': '/ws/CadConsultaCadastro4', + 'RecepcaoEvento': '/ws/NFeRecepcaoEvento4', + 'NFeAutorizacao': '/ws/NFeAutorizacao4', + 'NFeRetAutorizacao': '/ws/NFeRetAutorizacao4', + }, + 'MT': { + 'prod_server': 'homologacao.sefaz.mt.gov.br', + 'dev_server': 'homologacao.sefaz.mt.gov.br', + 'NfeInutilizacao': '/nfews/v2/services/NfeInutilizacao4?wsdl', + 'NfeConsultaProtocolo': '/nfews/v2/services/NfeConsulta4?wsdl', + 'NfeStatusServico': '/nfews/v2/services/NfeStatusServico4?wsdl', + 'NfeConsultaCadastro': '/nfews/v2/services/CadConsultaCadastro4?wsdl', + 'RecepcaoEvento': '/nfews/v2/services/RecepcaoEvento4?wsdl', + 'NFeAutorizacao': '/nfews/v2/services/NfeAutorizacao4?wsdl', + 'NFeRetAutorizacao': '/nfews/v2/services/NfeRetAutorizacao4?wsdl', + }, + 'PE': { + 'prod_server': 'nfehomolog.sefaz.pe.gov.br', + 'dev_server': 'nfehomolog.sefaz.pe.gov.br', + 'NfeInutilizacao': '/nfe-service/services/NFeInutilizacao4?wsdl', + 'NfeConsultaProtocolo': '/nfe-service/services/NFeConsultaProtocolo4?wsdl', + 'NfeStatusServico': '/nfe-service/services/NFeStatusServico4?wsdl', + 'NfeConsultaCadastro': '/nfe-service/services/CadConsultaCadastro4?wsdl', + 'RecepcaoEvento': '/nfe-service/services/NFeRecepcaoEvento4?wsdl', + 'NFeAutorizacao': '/nfe-service/services/NFeAutorizacao4?wsdl', + 'NFeRetAutorizacao': '/nfe-service/services/NFeRetAutorizacao4?wsdl', + }, + 'PR': { + 'prod_server': 'homologacao.nfe.sefa.pr.gov.br', + 'dev_server': 'homologacao.nfe.sefa.pr.gov.br', + 'NfeInutilizacao': '/nfe/NFeInutilizacao4?wsdl', + 'NfeConsultaProtocolo': '/nfe/NFeConsultaProtocolo4?wsdl', + 'NfeStatusServico': '/nfe/NFeStatusServico4?wsdl', + 'NfeConsultaCadastro': '/nfe/CadConsultaCadastro4?wsdl', + 'RecepcaoEvento': '/nfe/NFeRecepcaoEvento4?wsdl', + 'NFeAutorizacao': '/nfe/NFeAutorizacao4?wsdl', + 'NFeRetAutorizacao': '/nfe/NFeRetAutorizacao4?wsdl', + }, + 'RS': { + 'prod_server': 'nfe-homologacao.sefazrs.rs.gov.br', + 'dev_server': 'nfe-homologacao.sefazrs.rs.gov.br', + 'NfeInutilizacao': '/ws/nfeinutilizacao/nfeinutilizacao4.asmx', + 'NfeConsultaProtocolo': '/ws/NfeConsulta/NfeConsulta4.asmx', + 'NfeStatusServico': '/ws/NfeStatusServico/NfeStatusServico4.asmx', + 'NfeConsultaCadastro': '/ws/cadconsultacadastro/cadconsultacadastro4.asmx', + 'RecepcaoEvento': '/ws/recepcaoevento/recepcaoevento4.asmx', + 'NFeAutorizacao': '/ws/NfeAutorizacao/NFeAutorizacao4.asmx', + 'NFeRetAutorizacao': '/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx', + }, + 'SP': { + 'prod_server': 'homologacao.nfe.fazenda.sp.gov.br', + 'dev_server': 'homologacao.nfe.fazenda.sp.gov.br', + 'NfeInutilizacao': '/ws/nfeinutilizacao4.asmx', + 'NfeConsultaProtocolo': '/ws/nfeconsultaprotocolo4.asmx', + 'NfeStatusServico': '/ws/nfestatusservico4.asmx', + 'NfeConsultaCadastro': '/ws/cadconsultacadastro4.asmx', + 'RecepcaoEvento': '/ws/nferecepcaoevento4.asmx', + 'NFeAutorizacao': '/ws/nfeautorizacao4.asmx', + 'NFeRetAutorizacao': '/ws/nferetautorizacao4.asmx', + }, + 'SVAN': { + 'prod_server': 'hom.sefazvirtual.fazenda.gov.br', + 'dev_server': 'hom.sefazvirtual.fazenda.gov.br', + 'NfeInutilizacao': '/NFeInutilizacao4/NFeInutilizacao4.asmx', + 'NfeConsultaProtocolo': '/NFeConsultaProtocolo4/NFeConsultaProtocolo4.asmx', + 'NfeStatusServico': '/NFeStatusServico4/NFeStatusServico4.asmx', + 'RecepcaoEvento': '/NFeRecepcaoEvento4/NFeRecepcaoEvento4.asmx', + 'NFeAutorizacao': '/NFeAutorizacao4/NFeAutorizacao4.asmx', + 'NFeRetAutorizacao': '/NFeRetAutorizacao4/NFeRetAutorizacao4.asmx', + }, + 'SVRS': { + 'prod_server': 'nfe-homologacao.svrs.rs.gov.br', + 'dev_server': 'nfe-homologacao.svrs.rs.gov.br', + 'NfeInutilizacao': '/ws/nfeinutilizacao/nfeinutilizacao4.asmx', + 'NfeConsultaProtocolo': '/ws/NfeConsulta/NfeConsulta4.asmx', + 'NfeStatusServico': '/ws/NfeStatusServico/NfeStatusServico4.asmx', + 'NfeConsultaCadastro': '/ws/cadconsultacadastro/cadconsultacadastro4.asmx', + 'RecepcaoEvento': '/ws/recepcaoevento/recepcaoevento4.asmx', + 'NFeAutorizacao': '/ws/NfeAutorizacao/NFeAutorizacao4.asmx', + 'NFeRetAutorizacao': '/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx', + }, + 'SVC-AN': { + 'prod_server': 'hom.sefazvirtual.fazenda.gov.br', + 'dev_server': 'hom.sefazvirtual.fazenda.gov.br', + 'NfeInutilizacao': '/NFeInutilizacao4/NFeInutilizacao4.asmx', + 'NfeConsultaProtocolo': '/NFeConsultaProtocolo4/NFeConsultaProtocolo4.asmx', + 'NfeStatusServico': '/NFeStatusServico4/NFeStatusServico4.asmx', + 'RecepcaoEvento': '/NFeRecepcaoEvento4/NFeRecepcaoEvento4.asmx', + 'NFeAutorizacao': '/NFeAutorizacao4/NFeAutorizacao4.asmx', + 'NFeRetAutorizacao': '/NFeRetAutorizacao4/NFeRetAutorizacao4.asmx', + }, + 'SVC-RS': { + 'prod_server': 'nfe-homologacao.svrs.rs.gov.br', + 'dev_server': 'nfe-homologacao.svrs.rs.gov.br', + 'NfeConsultaProtocolo': '/ws/NfeConsulta/NfeConsulta4.asmx', + 'NfeStatusServico': '/ws/NfeStatusServico/NfeStatusServico4.asmx', + 'RecepcaoEvento': '/ws/recepcaoevento/recepcaoevento4.asmx', + 'NFeAutorizacao': '/ws/NfeAutorizacao/NFeAutorizacao4.asmx', + 'NFeRetAutorizacao': '/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx', + }, + 'AN': { + 'prod_server': 'hom1.nfe.fazenda.gov.br', + 'dev_server': 'hom1.nfe.fazenda.gov.br', + 'NFeDistribuicaoDFe': '/NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx', + 'RecepcaoEvento': '/NFeRecepcaoEvento4/NFeRecepcaoEvento4.asmx', + }, +} + diff --git a/nfelib/nfe/client/servers_scraper.py b/nfelib/nfe/client/servers_scraper.py new file mode 100644 index 00000000..3c3f686f --- /dev/null +++ b/nfelib/nfe/client/servers_scraper.py @@ -0,0 +1,98 @@ +import pandas as pd +import requests +from io import StringIO +from pathlib import Path + +import logging +import os +import time +from os import environ +from xsdata.formats.dataclass.serializers import PycodeSerializer +from unittest import TestCase +from bs4 import BeautifulSoup +from pprint import pp +import requests +import pandas as pd + +PROD_URL = "https://hom.nfe.fazenda.gov.br/portal/webServices.aspx" +DEV_URL = "https://hom.nfe.fazenda.gov.br/portal/webServices.aspx" + +OUTPUT_FILE = Path("nfelib/nfe/client/servers.py") + + +def fetch_servers(prod_url: str, dev_url: str) -> dict: + """Fetches the NFe server list from the webpage using pandas.""" + constants = "" + servers_list = [] + servers = {} + + prod_response = requests.get(prod_url) + soup = BeautifulSoup(prod_response.content, 'lxml') + captions = soup.find_all('caption') + + servers_list = [] + for caption in captions: + if "(" not in str(caption): + continue + servers_list.append(str(caption).split("(")[1].split(")")[0]) + + dev_response = requests.get(dev_url) + tables = pd.read_html(dev_response.content.decode(dev_response.apparent_encoding)) + dev_servers = {} + for index, table in enumerate(list(tables)): + if list(table.to_dict().keys())[0] != "Serviço": + continue + urls = list(dict(table.to_dict())["URL"].values()) + dev_server = urls[-1].split("/")[2] + server = servers_list[index] + dev_servers[server] = dev_server + + #response = requests.get("https://www.cte.fazenda.gov.br/portal/webServices.aspx?tipoConteudo=wpdBtfbTMrw=") + + tables = pd.read_html(prod_response.content.decode(prod_response.apparent_encoding)) + for index, table in enumerate(list(tables)): + if list(table.to_dict().keys())[0] != "Serviço": + continue + actions = list(dict(table.to_dict())["Serviço"].values()) + urls = list(dict(table.to_dict())["URL"].values()) + + if index == 0: + for action in actions: + if "qrcode" in action.lower(): + continue + constants += f'{action.upper()} = "{action}"\n' + paths = ["/" + "/".join(url.split("/")[3:]) for url in urls] + prod_server = urls[-1].split("/")[2] + + server = servers_list[index] + action_dict = {"prod_server": prod_server, "dev_server": dev_servers[server]} + for index, action in enumerate(actions): + if "qrcode" in action.lower(): + continue + action_dict[action] = paths[index] + + servers[server] = action_dict + + print("\n") + + serializer = PycodeSerializer() + servers = serializer.render(servers, var_name="servers") + print(constants) + print(servers) + return servers + + +def save_servers(servers: dict, output_file: Path) -> None: + """Saves the extracted server data as a Python dictionary.""" + content = f"# Auto-generated file. Do not edit manually.\nservers = {servers}\n" + output_file.write_text(content, encoding="utf-8") + + +def main(): + servers = fetch_servers(PROD_URL, DEV_URL) + save_servers(servers, OUTPUT_FILE) + + +if __name__ == "__main__": + main() + From 49fb3dfc8b912cc21c52f43ab95eea9e4b521bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 01:05:13 +0000 Subject: [PATCH 02/38] better scraper --- nfelib/nfe/client/servers.py | 57 +++++--- nfelib/nfe/client/servers_scraper.py | 191 ++++++++++++++++----------- 2 files changed, 152 insertions(+), 96 deletions(-) diff --git a/nfelib/nfe/client/servers.py b/nfelib/nfe/client/servers.py index 7c50005d..670388a4 100644 --- a/nfelib/nfe/client/servers.py +++ b/nfelib/nfe/client/servers.py @@ -1,8 +1,21 @@ # Auto-generated file. Do not edit manually. +# Constants +NFEINUTILIZACAO = "NfeInutilizacao" +NFECONSULTAPROTOCOLO = "NfeConsultaProtocolo" +NFESTATUSSERVICO = "NfeStatusServico" +NFECONSULTACADASTRO = "NfeConsultaCadastro" +RECEPCAOEVENTO = "RecepcaoEvento" +NFEAUTORIZACAO = "NFeAutorizacao" +NFERETAUTORIZACAO = "NFeRetAutorizacao" +NFEDISTRIBUICAODFE = "NFeDistribuicaoDFe" + +# Servers + + servers = { 'AM': { - 'prod_server': 'homnfe.sefaz.am.gov.br', + 'prod_server': 'nfe.sefaz.am.gov.br', 'dev_server': 'homnfe.sefaz.am.gov.br', 'NfeInutilizacao': '/services2/services/NfeInutilizacao4', 'NfeConsultaProtocolo': '/services2/services/NfeConsulta4', @@ -13,7 +26,7 @@ 'NFeRetAutorizacao': '/services2/services/NfeRetAutorizacao4', }, 'BA': { - 'prod_server': 'hnfe.sefaz.ba.gov.br', + 'prod_server': 'nfe.sefaz.ba.gov.br', 'dev_server': 'hnfe.sefaz.ba.gov.br', 'NfeInutilizacao': '/webservices/NFeInutilizacao4/NFeInutilizacao4.asmx', 'NfeConsultaProtocolo': '/webservices/NFeConsultaProtocolo4/NFeConsultaProtocolo4.asmx', @@ -24,7 +37,7 @@ 'NFeRetAutorizacao': '/webservices/NFeRetAutorizacao4/NFeRetAutorizacao4.asmx', }, 'GO': { - 'prod_server': 'homolog.sefaz.go.gov.br', + 'prod_server': 'nfe.sefaz.go.gov.br', 'dev_server': 'homolog.sefaz.go.gov.br', 'NfeInutilizacao': '/nfe/services/NFeInutilizacao4?wsdl', 'NfeConsultaProtocolo': '/nfe/services/NFeConsultaProtocolo4?wsdl', @@ -35,7 +48,7 @@ 'NFeRetAutorizacao': '/nfe/services/NFeRetAutorizacao4?wsdl', }, 'MG': { - 'prod_server': 'hnfe.fazenda.mg.gov.br', + 'prod_server': 'nfe.fazenda.mg.gov.br', 'dev_server': 'hnfe.fazenda.mg.gov.br', 'NfeInutilizacao': '/nfe2/services/NFeInutilizacao4', 'NfeConsultaProtocolo': '/nfe2/services/NFeConsultaProtocolo4', @@ -46,7 +59,7 @@ 'NFeRetAutorizacao': '/nfe2/services/NFeRetAutorizacao4', }, 'MS': { - 'prod_server': 'hom.nfe.sefaz.ms.gov.br', + 'prod_server': 'nfe.sefaz.ms.gov.br', 'dev_server': 'hom.nfe.sefaz.ms.gov.br', 'NfeInutilizacao': '/ws/NFeInutilizacao4', 'NfeConsultaProtocolo': '/ws/NFeConsultaProtocolo4', @@ -57,7 +70,7 @@ 'NFeRetAutorizacao': '/ws/NFeRetAutorizacao4', }, 'MT': { - 'prod_server': 'homologacao.sefaz.mt.gov.br', + 'prod_server': 'nfe.sefaz.mt.gov.br', 'dev_server': 'homologacao.sefaz.mt.gov.br', 'NfeInutilizacao': '/nfews/v2/services/NfeInutilizacao4?wsdl', 'NfeConsultaProtocolo': '/nfews/v2/services/NfeConsulta4?wsdl', @@ -68,18 +81,18 @@ 'NFeRetAutorizacao': '/nfews/v2/services/NfeRetAutorizacao4?wsdl', }, 'PE': { - 'prod_server': 'nfehomolog.sefaz.pe.gov.br', + 'prod_server': 'nfe.sefaz.pe.gov.br', 'dev_server': 'nfehomolog.sefaz.pe.gov.br', - 'NfeInutilizacao': '/nfe-service/services/NFeInutilizacao4?wsdl', - 'NfeConsultaProtocolo': '/nfe-service/services/NFeConsultaProtocolo4?wsdl', - 'NfeStatusServico': '/nfe-service/services/NFeStatusServico4?wsdl', + 'NfeInutilizacao': '/nfe-service/services/NFeInutilizacao4', + 'NfeConsultaProtocolo': '/nfe-service/services/NFeConsultaProtocolo4', + 'NfeStatusServico': '/nfe-service/services/NFeStatusServico4', 'NfeConsultaCadastro': '/nfe-service/services/CadConsultaCadastro4?wsdl', - 'RecepcaoEvento': '/nfe-service/services/NFeRecepcaoEvento4?wsdl', - 'NFeAutorizacao': '/nfe-service/services/NFeAutorizacao4?wsdl', - 'NFeRetAutorizacao': '/nfe-service/services/NFeRetAutorizacao4?wsdl', + 'RecepcaoEvento': '/nfe-service/services/NFeRecepcaoEvento4', + 'NFeAutorizacao': '/nfe-service/services/NFeAutorizacao4', + 'NFeRetAutorizacao': '/nfe-service/services/NFeRetAutorizacao4', }, 'PR': { - 'prod_server': 'homologacao.nfe.sefa.pr.gov.br', + 'prod_server': 'nfe.sefa.pr.gov.br', 'dev_server': 'homologacao.nfe.sefa.pr.gov.br', 'NfeInutilizacao': '/nfe/NFeInutilizacao4?wsdl', 'NfeConsultaProtocolo': '/nfe/NFeConsultaProtocolo4?wsdl', @@ -90,7 +103,7 @@ 'NFeRetAutorizacao': '/nfe/NFeRetAutorizacao4?wsdl', }, 'RS': { - 'prod_server': 'nfe-homologacao.sefazrs.rs.gov.br', + 'prod_server': 'nfe.sefazrs.rs.gov.br', 'dev_server': 'nfe-homologacao.sefazrs.rs.gov.br', 'NfeInutilizacao': '/ws/nfeinutilizacao/nfeinutilizacao4.asmx', 'NfeConsultaProtocolo': '/ws/NfeConsulta/NfeConsulta4.asmx', @@ -101,7 +114,7 @@ 'NFeRetAutorizacao': '/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx', }, 'SP': { - 'prod_server': 'homologacao.nfe.fazenda.sp.gov.br', + 'prod_server': 'nfe.fazenda.sp.gov.br', 'dev_server': 'homologacao.nfe.fazenda.sp.gov.br', 'NfeInutilizacao': '/ws/nfeinutilizacao4.asmx', 'NfeConsultaProtocolo': '/ws/nfeconsultaprotocolo4.asmx', @@ -112,7 +125,7 @@ 'NFeRetAutorizacao': '/ws/nferetautorizacao4.asmx', }, 'SVAN': { - 'prod_server': 'hom.sefazvirtual.fazenda.gov.br', + 'prod_server': 'www.sefazvirtual.fazenda.gov.br', 'dev_server': 'hom.sefazvirtual.fazenda.gov.br', 'NfeInutilizacao': '/NFeInutilizacao4/NFeInutilizacao4.asmx', 'NfeConsultaProtocolo': '/NFeConsultaProtocolo4/NFeConsultaProtocolo4.asmx', @@ -122,7 +135,7 @@ 'NFeRetAutorizacao': '/NFeRetAutorizacao4/NFeRetAutorizacao4.asmx', }, 'SVRS': { - 'prod_server': 'nfe-homologacao.svrs.rs.gov.br', + 'prod_server': 'nfe.svrs.rs.gov.br', 'dev_server': 'nfe-homologacao.svrs.rs.gov.br', 'NfeInutilizacao': '/ws/nfeinutilizacao/nfeinutilizacao4.asmx', 'NfeConsultaProtocolo': '/ws/NfeConsulta/NfeConsulta4.asmx', @@ -133,7 +146,7 @@ 'NFeRetAutorizacao': '/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx', }, 'SVC-AN': { - 'prod_server': 'hom.sefazvirtual.fazenda.gov.br', + 'prod_server': 'www.sefazvirtual.fazenda.gov.br', 'dev_server': 'hom.sefazvirtual.fazenda.gov.br', 'NfeInutilizacao': '/NFeInutilizacao4/NFeInutilizacao4.asmx', 'NfeConsultaProtocolo': '/NFeConsultaProtocolo4/NFeConsultaProtocolo4.asmx', @@ -143,7 +156,7 @@ 'NFeRetAutorizacao': '/NFeRetAutorizacao4/NFeRetAutorizacao4.asmx', }, 'SVC-RS': { - 'prod_server': 'nfe-homologacao.svrs.rs.gov.br', + 'prod_server': 'nfe.svrs.rs.gov.br', 'dev_server': 'nfe-homologacao.svrs.rs.gov.br', 'NfeConsultaProtocolo': '/ws/NfeConsulta/NfeConsulta4.asmx', 'NfeStatusServico': '/ws/NfeStatusServico/NfeStatusServico4.asmx', @@ -152,10 +165,10 @@ 'NFeRetAutorizacao': '/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx', }, 'AN': { - 'prod_server': 'hom1.nfe.fazenda.gov.br', + 'prod_server': 'www.nfe.fazenda.gov.br', 'dev_server': 'hom1.nfe.fazenda.gov.br', - 'NFeDistribuicaoDFe': '/NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx', 'RecepcaoEvento': '/NFeRecepcaoEvento4/NFeRecepcaoEvento4.asmx', + 'NFeDistribuicaoDFe': '/NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx', }, } diff --git a/nfelib/nfe/client/servers_scraper.py b/nfelib/nfe/client/servers_scraper.py index 3c3f686f..8d0da386 100644 --- a/nfelib/nfe/client/servers_scraper.py +++ b/nfelib/nfe/client/servers_scraper.py @@ -1,98 +1,141 @@ -import pandas as pd -import requests -from io import StringIO +import logging from pathlib import Path +from typing import Dict, Any +from io import StringIO # Add this import -import logging -import os -import time -from os import environ -from xsdata.formats.dataclass.serializers import PycodeSerializer -from unittest import TestCase -from bs4 import BeautifulSoup -from pprint import pp -import requests import pandas as pd +import requests +from bs4 import BeautifulSoup +from xsdata.formats.dataclass.serializers import PycodeSerializer -PROD_URL = "https://hom.nfe.fazenda.gov.br/portal/webServices.aspx" +# Constants +PROD_URL = "https://www.nfe.fazenda.gov.br/portal/webServices.aspx" DEV_URL = "https://hom.nfe.fazenda.gov.br/portal/webServices.aspx" - OUTPUT_FILE = Path("nfelib/nfe/client/servers.py") +SERVICE_COLUMN = "Serviço" +URL_COLUMN = "URL" +QRCODE = "qrcode" -def fetch_servers(prod_url: str, dev_url: str) -> dict: - """Fetches the NFe server list from the webpage using pandas.""" - constants = "" - servers_list = [] - servers = {} +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) - prod_response = requests.get(prod_url) - soup = BeautifulSoup(prod_response.content, 'lxml') - captions = soup.find_all('caption') - - servers_list = [] - for caption in captions: - if "(" not in str(caption): - continue - servers_list.append(str(caption).split("(")[1].split(")")[0]) - - dev_response = requests.get(dev_url) - tables = pd.read_html(dev_response.content.decode(dev_response.apparent_encoding)) - dev_servers = {} - for index, table in enumerate(list(tables)): - if list(table.to_dict().keys())[0] != "Serviço": - continue - urls = list(dict(table.to_dict())["URL"].values()) - dev_server = urls[-1].split("/")[2] - server = servers_list[index] - dev_servers[server] = dev_server - - #response = requests.get("https://www.cte.fazenda.gov.br/portal/webServices.aspx?tipoConteudo=wpdBtfbTMrw=") - - tables = pd.read_html(prod_response.content.decode(prod_response.apparent_encoding)) - for index, table in enumerate(list(tables)): - if list(table.to_dict().keys())[0] != "Serviço": - continue - actions = list(dict(table.to_dict())["Serviço"].values()) - urls = list(dict(table.to_dict())["URL"].values()) - - if index == 0: - for action in actions: - if "qrcode" in action.lower(): - continue - constants += f'{action.upper()} = "{action}"\n' - paths = ["/" + "/".join(url.split("/")[3:]) for url in urls] - prod_server = urls[-1].split("/")[2] - - server = servers_list[index] - action_dict = {"prod_server": prod_server, "dev_server": dev_servers[server]} - for index, action in enumerate(actions): - if "qrcode" in action.lower(): + +def fetch_servers(prod_url: str, dev_url: str) -> Dict[str, Any]: + """Fetches the NFe server list from the webpage using pandas and BeautifulSoup.""" + servers = {} + constants = {} # To store dynamically generated constants + + try: + # Fetch production servers + prod_response = requests.get(prod_url) + prod_response.raise_for_status() + soup = BeautifulSoup(prod_response.content, 'lxml') + captions = soup.find_all('caption') + + servers_list = [ + str(caption).split("(")[1].split(")")[0] + for caption in captions if "(" in str(caption) + ] + + # Fetch development servers + dev_response = requests.get(dev_url) + dev_response.raise_for_status() + dev_html = dev_response.content.decode(dev_response.apparent_encoding) + dev_tables = pd.read_html(StringIO(dev_html)) # Wrap HTML in StringIO + + dev_servers = { + servers_list[index]: urls[-1].split("/")[2] + for index, table in enumerate(dev_tables) + if SERVICE_COLUMN in table.columns + for urls in [list(table.to_dict()[URL_COLUMN].values())] + } + + # Fetch production server details and generate constants + prod_html = prod_response.content.decode(prod_response.apparent_encoding) + prod_tables = pd.read_html(StringIO(prod_html)) # Wrap HTML in StringIO + + for index, table in enumerate(prod_tables): + if SERVICE_COLUMN not in table.columns or URL_COLUMN not in table.columns: + logger.warning(f"Skipping table {index}: missing required columns.") continue - action_dict[action] = paths[index] - servers[server] = action_dict + actions = list(dict(table.to_dict())[SERVICE_COLUMN].values()) + urls = list(dict(table.to_dict())[URL_COLUMN].values()) - print("\n") + # Generate constants from the first table + if index == 0: + for action in actions: + if QRCODE in action.lower(): + continue + constant_name = action.upper().replace(" ", "_") + constants[constant_name] = action + paths = ["/" + "/".join(url.split("/")[3:]) for url in urls] + prod_server = urls[-1].split("/")[2] + + server = servers_list[index] + action_dict = {"prod_server": prod_server, "dev_server": dev_servers[server]} + + # Use dynamically generated constants as keys + for action, path in zip(actions, paths): + if QRCODE in action.lower(): + continue + constant_name = action.upper().replace(" ", "_") + if constant_name in constants: + action_dict[constants[constant_name]] = path + + # Handle special case for Ambiente Nacional (AN) + if server == "AN": + # Ensure NFeDistribuicaoDFe is included + if "NFeDistribuicaoDFe" in actions: + constant_name = "NFEDISTRIBUICAODFE" + if constant_name not in constants: + constants[constant_name] = "NFeDistribuicaoDFe" + action_dict[constants[constant_name]] = paths[actions.index("NFeDistribuicaoDFe")] + + servers[server] = action_dict + + logger.info("Successfully fetched servers.") + return servers, constants + + except requests.RequestException as e: + logger.error(f"Failed to fetch servers: {e}") + return {}, {} + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") + return {}, {} + + +def save_servers(servers: Dict[str, Any], constants: Dict[str, str], output_file: Path) -> None: + """Saves the extracted server data and constants as a Python file.""" + # Generate the constants section + constants_section = "\n".join([f'{key} = "{value}"' for key, value in constants.items()]) + + # Use PycodeSerializer to format the servers dictionary serializer = PycodeSerializer() - servers = serializer.render(servers, var_name="servers") - print(constants) - print(servers) - return servers + formatted_servers = serializer.render(servers, var_name="servers") + # Write the formatted output to the file + content = f"""# Auto-generated file. Do not edit manually. -def save_servers(servers: dict, output_file: Path) -> None: - """Saves the extracted server data as a Python dictionary.""" - content = f"# Auto-generated file. Do not edit manually.\nservers = {servers}\n" +# Constants +{constants_section} + +# Servers +{formatted_servers} +""" + output_file.parent.mkdir(parents=True, exist_ok=True) output_file.write_text(content, encoding="utf-8") + logger.info(f"Servers and constants saved to {output_file}") def main(): - servers = fetch_servers(PROD_URL, DEV_URL) - save_servers(servers, OUTPUT_FILE) + servers, constants = fetch_servers(PROD_URL, DEV_URL) + if servers: + save_servers(servers, constants, OUTPUT_FILE) if __name__ == "__main__": main() - From a97def09f5e5957a40d9df005a1565c1833d783d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 03:51:31 +0000 Subject: [PATCH 03/38] test NF-e servers --- tests/nfe/test_servers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/nfe/test_servers.py diff --git a/tests/nfe/test_servers.py b/tests/nfe/test_servers.py new file mode 100644 index 00000000..121d2761 --- /dev/null +++ b/tests/nfe/test_servers.py @@ -0,0 +1,16 @@ +import subprocess +from pathlib import Path + +OUTPUT_FILE = Path("nfelib/nfe/client/servers.py") + +def read_current_servers(): + return OUTPUT_FILE.read_text(encoding="utf-8") if OUTPUT_FILE.exists() else "" + +def test_scraper(): + old_content = read_current_servers() + + subprocess.run(["python", "nfelib/nfe/client/servers_scraper.py"], check=True) + + new_content = read_current_servers() + assert new_content == old_content, "Server list has changed. Review and commit the new file." + From c33bb1b2c97cd472d7579e7ab4e045545038ed2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 03:55:44 +0000 Subject: [PATCH 04/38] add SOAP test deps --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f0ff0658..f9b1c63f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,8 @@ test = [ "beautifulsoup4", "erpbrasil.assinatura", "brazilfiscalreport", + "beautifulsoup4", + "pandas", ] [tool.setuptools] From 9d3bfca0f235ad32d3690c124b4675de52350436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 05:56:41 +0000 Subject: [PATCH 05/38] initial generic WSDL downloader --- nfelib/wsdl_downloader.py | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 nfelib/wsdl_downloader.py diff --git a/nfelib/wsdl_downloader.py b/nfelib/wsdl_downloader.py new file mode 100644 index 00000000..278e49f5 --- /dev/null +++ b/nfelib/wsdl_downloader.py @@ -0,0 +1,92 @@ +import logging +import os +from os import environ +import urllib3 + +from requests import Session +from requests_pkcs12 import Pkcs12Adapter + +# Configure logging +_logger = logging.getLogger(__name__) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +# Constants +SERVER = "https://nfe-homologacao.svrs.rs.gov.br" # TODO: Make this a parameter +WSDL_DIRS = { + "nfe": ("nfelib/nfe/wsdl/v4_0", "nfe"), + "mdfe": ("nfelib/mdfe/wsdl/v3_0", "mdfe"), + "cte": ("nfelib/cte/wsdl/v4_0", "cte"), + "bpe": ("nfelib/bpe/wsdl/v1_0", "bpe"), +} + + +def download_wsdl_files(*wsdl_urls): + """Download WSDL files for NF-e, CT-e, MDF-e, and BP-e.""" + + # Access the certificate and password from environment variables + CERT_FILE = environ.get("CERT_FILE") + CERT_PASSWORD = environ.get("CERT_PASSWORD") + + if not CERT_FILE or not CERT_PASSWORD: + raise ValueError( + "Certificate file or password not provided in environment variables." + ) + + session = Session() + session.verify = False # Disable SSL verification (use with caution) + + for url in wsdl_urls: + try: + # Determine the server and mount the PKCS12 adapter + if not url.startswith("http"): + url = SERVER + url + server = SERVER + else: + server = "https://" + url.split("/")[2] + + session.mount( + server, + Pkcs12Adapter( + pkcs12_filename=CERT_FILE, + pkcs12_password=CERT_PASSWORD, + ), + ) + + # Ensure the URL ends with ?wsdl + if not url.endswith("?wsdl"): + url += "?wsdl" + + # Fetch the WSDL content + response = session.get(url) + response.raise_for_status() # Raise an exception for HTTP errors + + # Determine the output file path + filename = ( + url.split("/")[-1] + .replace("?wsdl", "") + .replace(".asmx", ".wsdl") + .lower() + ) + + # Identify the document type (NF-e, CT-e, MDF-e, BP-e) + doc_type = None + for key, (_, prefix) in WSDL_DIRS.items(): + if key in url.lower(): + doc_type = key + break + if not doc_type: + raise ValueError(f"Cannot determine document type for URL: {url}") + + # Create the output directory if it doesn't exist + wsdl_dir, _ = WSDL_DIRS[doc_type] + os.makedirs(wsdl_dir, exist_ok=True) + + # Write the WSDL content to the file + wsdl_file = os.path.join(wsdl_dir, filename) + _logger.info(f"Writing to {wsdl_file}") + with open(wsdl_file, "w") as file: + file.write(response.text) + + except Exception as e: + _logger.error(f"Failed to download or save WSDL from {url}: {e}") + raise From ecd857b09ceda115eb1cb5583bf3cbc398b2d13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 05:57:33 +0000 Subject: [PATCH 06/38] initial NF-e WSDL downloader --- nfelib/nfe/client/wsdl_downloader.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 nfelib/nfe/client/wsdl_downloader.py diff --git a/nfelib/nfe/client/wsdl_downloader.py b/nfelib/nfe/client/wsdl_downloader.py new file mode 100644 index 00000000..9317831c --- /dev/null +++ b/nfelib/nfe/client/wsdl_downloader.py @@ -0,0 +1,17 @@ +import logging +import importlib +from nfelib.wsdl_downloader import download_wsdl_files + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + download_wsdl_files( + # NF-e + "https://nfe.svrs.rs.gov.br/ws/nfeinutilizacao/nfeinutilizacao4.asmx", + "https://nfe.svrs.rs.gov.br/ws/NfeConsulta/NfeConsulta4.asmx", + "https://nfe.svrs.rs.gov.br/ws/NfeStatusServico/NfeStatusServico4.asmx", + # "https://nfe.svrs.rs.gov.br/ws/cadconsultacadastro/cadconsultacadastro4.asmx", + "https://nfe.svrs.rs.gov.br/ws/recepcaoevento/recepcaoevento4.asmx", + "https://nfe.svrs.rs.gov.br/ws/NfeAutorizacao/NFeAutorizacao4.asmx", + "https://nfe.svrs.rs.gov.br/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx", + "https://www1.nfe.fazenda.gov.br/NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx", + ) From 7419601e5a1559ea8f9cdcccb952d513d0937cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 15:39:18 +0000 Subject: [PATCH 07/38] refactor: reusable servers_scrapers --- nfelib/nfe/client/servers_scraper.py | 118 +---------------------- nfelib/utils/__init__.py | 0 nfelib/utils/servers_scraper.py | 136 +++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 117 deletions(-) create mode 100644 nfelib/utils/__init__.py create mode 100644 nfelib/utils/servers_scraper.py diff --git a/nfelib/nfe/client/servers_scraper.py b/nfelib/nfe/client/servers_scraper.py index 8d0da386..5558db47 100644 --- a/nfelib/nfe/client/servers_scraper.py +++ b/nfelib/nfe/client/servers_scraper.py @@ -7,129 +7,13 @@ import requests from bs4 import BeautifulSoup from xsdata.formats.dataclass.serializers import PycodeSerializer +from nfelib.utils.servers_scraper import fetch_servers, save_servers # Constants PROD_URL = "https://www.nfe.fazenda.gov.br/portal/webServices.aspx" DEV_URL = "https://hom.nfe.fazenda.gov.br/portal/webServices.aspx" OUTPUT_FILE = Path("nfelib/nfe/client/servers.py") -SERVICE_COLUMN = "Serviço" -URL_COLUMN = "URL" -QRCODE = "qrcode" - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - - -def fetch_servers(prod_url: str, dev_url: str) -> Dict[str, Any]: - """Fetches the NFe server list from the webpage using pandas and BeautifulSoup.""" - servers = {} - constants = {} # To store dynamically generated constants - - try: - # Fetch production servers - prod_response = requests.get(prod_url) - prod_response.raise_for_status() - soup = BeautifulSoup(prod_response.content, 'lxml') - captions = soup.find_all('caption') - - servers_list = [ - str(caption).split("(")[1].split(")")[0] - for caption in captions if "(" in str(caption) - ] - - # Fetch development servers - dev_response = requests.get(dev_url) - dev_response.raise_for_status() - dev_html = dev_response.content.decode(dev_response.apparent_encoding) - dev_tables = pd.read_html(StringIO(dev_html)) # Wrap HTML in StringIO - - dev_servers = { - servers_list[index]: urls[-1].split("/")[2] - for index, table in enumerate(dev_tables) - if SERVICE_COLUMN in table.columns - for urls in [list(table.to_dict()[URL_COLUMN].values())] - } - - # Fetch production server details and generate constants - prod_html = prod_response.content.decode(prod_response.apparent_encoding) - prod_tables = pd.read_html(StringIO(prod_html)) # Wrap HTML in StringIO - - for index, table in enumerate(prod_tables): - if SERVICE_COLUMN not in table.columns or URL_COLUMN not in table.columns: - logger.warning(f"Skipping table {index}: missing required columns.") - continue - - actions = list(dict(table.to_dict())[SERVICE_COLUMN].values()) - urls = list(dict(table.to_dict())[URL_COLUMN].values()) - - # Generate constants from the first table - if index == 0: - for action in actions: - if QRCODE in action.lower(): - continue - constant_name = action.upper().replace(" ", "_") - constants[constant_name] = action - - paths = ["/" + "/".join(url.split("/")[3:]) for url in urls] - prod_server = urls[-1].split("/")[2] - - server = servers_list[index] - action_dict = {"prod_server": prod_server, "dev_server": dev_servers[server]} - - # Use dynamically generated constants as keys - for action, path in zip(actions, paths): - if QRCODE in action.lower(): - continue - constant_name = action.upper().replace(" ", "_") - if constant_name in constants: - action_dict[constants[constant_name]] = path - - # Handle special case for Ambiente Nacional (AN) - if server == "AN": - # Ensure NFeDistribuicaoDFe is included - if "NFeDistribuicaoDFe" in actions: - constant_name = "NFEDISTRIBUICAODFE" - if constant_name not in constants: - constants[constant_name] = "NFeDistribuicaoDFe" - action_dict[constants[constant_name]] = paths[actions.index("NFeDistribuicaoDFe")] - - servers[server] = action_dict - - logger.info("Successfully fetched servers.") - return servers, constants - - except requests.RequestException as e: - logger.error(f"Failed to fetch servers: {e}") - return {}, {} - except Exception as e: - logger.error(f"An unexpected error occurred: {e}") - return {}, {} - - -def save_servers(servers: Dict[str, Any], constants: Dict[str, str], output_file: Path) -> None: - """Saves the extracted server data and constants as a Python file.""" - # Generate the constants section - constants_section = "\n".join([f'{key} = "{value}"' for key, value in constants.items()]) - - # Use PycodeSerializer to format the servers dictionary - serializer = PycodeSerializer() - formatted_servers = serializer.render(servers, var_name="servers") - - # Write the formatted output to the file - content = f"""# Auto-generated file. Do not edit manually. - -# Constants -{constants_section} - -# Servers -{formatted_servers} -""" - output_file.parent.mkdir(parents=True, exist_ok=True) - output_file.write_text(content, encoding="utf-8") - logger.info(f"Servers and constants saved to {output_file}") - def main(): servers, constants = fetch_servers(PROD_URL, DEV_URL) diff --git a/nfelib/utils/__init__.py b/nfelib/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nfelib/utils/servers_scraper.py b/nfelib/utils/servers_scraper.py new file mode 100644 index 00000000..d93edc09 --- /dev/null +++ b/nfelib/utils/servers_scraper.py @@ -0,0 +1,136 @@ +import logging +from pathlib import Path +from typing import Dict, Any +from io import StringIO # Add this import + +import pandas as pd +import requests +from bs4 import BeautifulSoup +from xsdata.formats.dataclass.serializers import PycodeSerializer + +SERVICE_COLUMN = "Serviço" +URL_COLUMN = "URL" +QRCODE = "qrcode" + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def fetch_servers(prod_url: str, dev_url: str) -> Dict[str, Any]: + """Fetches the NFe server list from the webpage using pandas and BeautifulSoup.""" + servers = {} + constants = {} # To store dynamically generated constants + + try: + # Fetch production servers + prod_response = requests.get(prod_url) + prod_response.raise_for_status() + soup = BeautifulSoup(prod_response.content, "lxml") + captions = soup.find_all("caption") + + servers_list = [ + str(caption).split("(")[1].split(")")[0] + for caption in captions + if "(" in str(caption) + ] + + # Fetch development servers + dev_response = requests.get(dev_url) + dev_response.raise_for_status() + dev_html = dev_response.content.decode(dev_response.apparent_encoding) + dev_tables = pd.read_html(StringIO(dev_html)) # Wrap HTML in StringIO + + dev_servers = { + servers_list[index]: urls[-1].split("/")[2] + for index, table in enumerate(dev_tables) + if SERVICE_COLUMN in table.columns + for urls in [list(table.to_dict()[URL_COLUMN].values())] + } + + # Fetch production server details and generate constants + prod_html = prod_response.content.decode(prod_response.apparent_encoding) + prod_tables = pd.read_html(StringIO(prod_html)) # Wrap HTML in StringIO + + for index, table in enumerate(prod_tables): + if SERVICE_COLUMN not in table.columns or URL_COLUMN not in table.columns: + logger.warning(f"Skipping table {index}: missing required columns.") + continue + + actions = list(dict(table.to_dict())[SERVICE_COLUMN].values()) + urls = list(dict(table.to_dict())[URL_COLUMN].values()) + + # Generate constants from the first table + if index == 0: + for action in actions: + if QRCODE in action.lower(): + continue + constant_name = action.upper().replace(" ", "_") + constants[constant_name] = action + + paths = ["/" + "/".join(url.split("/")[3:]) for url in urls] + prod_server = urls[-1].split("/")[2] + + server = servers_list[index] + action_dict = { + "prod_server": prod_server, + "dev_server": dev_servers[server], + } + + # Use dynamically generated constants as keys + for action, path in zip(actions, paths): + if QRCODE in action.lower(): + continue + constant_name = action.upper().replace(" ", "_") + if constant_name in constants: + action_dict[constants[constant_name]] = path + + # Handle special case for Ambiente Nacional (AN) + if server == "AN": + # Ensure NFeDistribuicaoDFe is included + if "NFeDistribuicaoDFe" in actions: + constant_name = "NFEDISTRIBUICAODFE" + if constant_name not in constants: + constants[constant_name] = "NFeDistribuicaoDFe" + action_dict[constants[constant_name]] = paths[ + actions.index("NFeDistribuicaoDFe") + ] + + servers[server] = action_dict + + logger.info("Successfully fetched servers.") + return servers, constants + + except requests.RequestException as e: + logger.error(f"Failed to fetch servers: {e}") + return {}, {} + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") + return {}, {} + + +def save_servers( + servers: Dict[str, Any], constants: Dict[str, str], output_file: Path +) -> None: + """Saves the extracted server data and constants as a Python file.""" + # Generate the constants section + constants_section = "\n".join( + [f'{key} = "{value}"' for key, value in constants.items()] + ) + + # Use PycodeSerializer to format the servers dictionary + serializer = PycodeSerializer() + formatted_servers = serializer.render(servers, var_name="servers") + + # Write the formatted output to the file + content = f"""# Auto-generated file. Do not edit manually. + +# Constants +{constants_section} + +# Servers +{formatted_servers} +""" + output_file.parent.mkdir(parents=True, exist_ok=True) + output_file.write_text(content, encoding="utf-8") + logger.info(f"Servers and constants saved to {output_file}") From f986ba9a49400e8216162fc2757c9193080cdd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 15:41:40 +0000 Subject: [PATCH 08/38] add CT-e scraper/servers --- nfelib/cte/client/__init__.py | 0 nfelib/cte/client/servers.py | 109 +++++++++++++++++++++++++++ nfelib/cte/client/servers_scraper.py | 25 ++++++ 3 files changed, 134 insertions(+) create mode 100644 nfelib/cte/client/__init__.py create mode 100644 nfelib/cte/client/servers.py create mode 100644 nfelib/cte/client/servers_scraper.py diff --git a/nfelib/cte/client/__init__.py b/nfelib/cte/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nfelib/cte/client/servers.py b/nfelib/cte/client/servers.py new file mode 100644 index 00000000..fff938d9 --- /dev/null +++ b/nfelib/cte/client/servers.py @@ -0,0 +1,109 @@ +# Auto-generated file. Do not edit manually. + +# Constants +CTESTATUSSERVICOV4 = "CteStatusServicoV4" +CTECONSULTAV4 = "CteConsultaV4" +CTERECEPCAOEVENTOV4 = "CteRecepcaoEventoV4" +CTERECEPCAOOSV4 = "CTeRecepcaoOSV4" +CTERECEPCAOSINCV4 = "CTeRecepcaoSincV4" +CTERECEPCAOGTVEV4 = "CTeRecepcaoGTVeV4" +CTERECEPCAOSIMPV4 = "CTeRecepcaoSimpV4" + +# Servers + + +servers = { + 'MT': { + 'prod_server': 'cte.sefaz.mt.gov.br', + 'dev_server': 'homologacao.sefaz.mt.gov.br', + 'CteStatusServicoV4': '/ctews2/services/CTeStatusServicoV4?wsdl', + 'CteConsultaV4': '/ctews2/services/CTeConsultaV4?wsdl', + 'CteRecepcaoEventoV4': '/ctews2/services/CTeRecepcaoEventoV4?wsdl', + 'CTeRecepcaoOSV4': '/ctews/services/CTeRecepcaoOSV4?wsdl', + 'CTeRecepcaoSincV4': '/ctews2/services/CTeRecepcaoSincV4?wsdl', + 'CTeRecepcaoGTVeV4': '/ctews2/services/CTeRecepcaoGTVeV4?wsdl', + 'CTeRecepcaoSimpV4': '/cte-ws/services/CTeRecepcaoSimpV4', + }, + 'MS': { + 'prod_server': 'producao.cte.ms.gov.br', + 'dev_server': 'homologacao.cte.ms.gov.br', + 'CteStatusServicoV4': '/ws/CTeStatusServicoV4', + 'CteConsultaV4': '/ws/CTeConsultaV4', + 'CteRecepcaoEventoV4': '/ws/CTeRecepcaoEventoV4', + 'CTeRecepcaoOSV4': '/ws/CTeRecepcaoOSV4', + 'CTeRecepcaoSincV4': '/ws/CTeRecepcaoSincV4', + 'CTeRecepcaoGTVeV4': '/ws/CTeRecepcaoGTVeV4', + 'CTeRecepcaoSimpV4': '/ws/CTeRecepcaoSimpV4', + }, + 'MG': { + 'prod_server': 'cte.fazenda.mg.gov.br', + 'dev_server': 'hcte.fazenda.mg.gov.br', + 'CteStatusServicoV4': '/cte/services/CTeStatusServicoV4', + 'CteConsultaV4': '/cte/services/CTeConsultaV4', + 'CteRecepcaoEventoV4': '/cte/services/CTeRecepcaoEventoV4', + 'CTeRecepcaoOSV4': '/cte/services/CTeRecepcaoOSV4', + 'CTeRecepcaoSincV4': '/cte/services/CTeRecepcaoSincV4', + 'CTeRecepcaoGTVeV4': '/cte/services/CTeRecepcaoGTVeV4', + 'CTeRecepcaoSimpV4': '/cte/services/CTeRecepcaoSimpV4', + }, + 'PR': { + 'prod_server': 'cte.fazenda.pr.gov.br', + 'dev_server': 'homologacao.cte.fazenda.pr.gov.br', + 'CteStatusServicoV4': '/cte4/CTeStatusServicoV4?wsdl', + 'CteConsultaV4': '/cte4/CTeConsultaV4?wsdl', + 'CteRecepcaoEventoV4': '/cte4/CTeRecepcaoEventoV4?wsdl', + 'CTeRecepcaoOSV4': '/cte4/CTeRecepcaoOSV4?wsdl', + 'CTeRecepcaoSincV4': '/cte4/CTeRecepcaoSincV4?wsdl', + 'CTeRecepcaoGTVeV4': '/cte4/CTeRecepcaoGTVeV4?wsdl', + 'CTeRecepcaoSimpV4': '/cte4/CTeRecepcaoSimpV4', + }, + 'RS': { + 'prod_server': 'cte.svrs.rs.gov.br', + 'dev_server': 'cte-homologacao.svrs.rs.gov.br', + 'CteStatusServicoV4': '/ws/CTeStatusServicoV4/CTeStatusServicoV4.asmx', + 'CteConsultaV4': '/ws/CTeConsultaV4/CTeConsultaV4.asmx', + 'CteRecepcaoEventoV4': '/ws/CTeRecepcaoEventoV4/CTeRecepcaoEventoV4.asmx', + 'CTeRecepcaoOSV4': '/ws/CTeRecepcaoOSV4/CTeRecepcaoOSV4.asmx', + 'CTeRecepcaoSincV4': '/ws/CTeRecepcaoSincV4/CTeRecepcaoSincV4.asmx', + 'CTeRecepcaoGTVeV4': '/ws/CTeRecepcaoGTVeV4/CTeRecepcaoGTVeV4.asmx', + 'CTeRecepcaoSimpV4': '/ws/CTeRecepcaoSimpV4/CTeRecepcaoSimpV4.asmx', + }, + 'SP': { + 'prod_server': 'nfe.fazenda.sp.gov.br', + 'dev_server': 'homologacao.nfe.fazenda.sp.gov.br', + 'CteStatusServicoV4': '/CTeWS/WS/CTeStatusServicoV4.asmx', + 'CteConsultaV4': '/CTeWS/WS/CTeConsultaV4.asmx', + 'CteRecepcaoEventoV4': '/CTeWS/WS/CTeRecepcaoEventoV4.asmx', + 'CTeRecepcaoOSV4': '/CTeWS/WS/CTeRecepcaoOSV4.asmx', + 'CTeRecepcaoSincV4': '/CTeWS/WS/CTeRecepcaoSincV4.asmx', + 'CTeRecepcaoGTVeV4': '/CTeWS/WS/CTeRecepcaoGTVeV4.asmx', + 'CTeRecepcaoSimpV4': '/CTeWS/WS/CTeRecepcaoSimpV4.asmx', + }, + 'SVRS': { + 'prod_server': 'cte.svrs.rs.gov.br', + 'dev_server': 'cte-homologacao.svrs.rs.gov.br', + 'CteStatusServicoV4': '/ws/CTeStatusServicoV4/CTeStatusServicoV4.asmx', + 'CteConsultaV4': '/ws/CTeConsultaV4/CTeConsultaV4.asmx', + 'CteRecepcaoEventoV4': '/ws/CTeRecepcaoEventoV4/CTeRecepcaoEventoV4.asmx', + 'CTeRecepcaoOSV4': '/ws/CTeRecepcaoOSV4/CTeRecepcaoOSV4.asmx', + 'CTeRecepcaoSincV4': '/ws/CTeRecepcaoSincV4/CTeRecepcaoSincV4.asmx', + 'CTeRecepcaoGTVeV4': '/ws/CTeRecepcaoGTVeV4/CTeRecepcaoGTVeV4.asmx', + 'CTeRecepcaoSimpV4': '/ws/CTeRecepcaoSimpV4/CTeRecepcaoSimpV4.asmx', + }, + 'SVSP': { + 'prod_server': 'nfe.fazenda.sp.gov.br', + 'dev_server': 'homologacao.nfe.fazenda.sp.gov.br', + 'CteStatusServicoV4': '/CTeWS/WS/CTeStatusServicoV4.asmx', + 'CteConsultaV4': '/CTeWS/WS/CTeConsultaV4.asmx', + 'CteRecepcaoEventoV4': '/CTeWS/WS/CTeRecepcaoEventoV4.asmx', + 'CTeRecepcaoOSV4': '/CTeWS/WS/CTeRecepcaoOSV4.asmx', + 'CTeRecepcaoSincV4': '/CTeWS/WS/CTeRecepcaoSincV4.asmx', + 'CTeRecepcaoGTVeV4': '/CTeWS/WS/CTeRecepcaoGTVeV4.asmx', + 'CTeRecepcaoSimpV4': '/CTeWS/WS/CTeRecepcaoSimpV4.asmx', + }, + 'AN': { + 'prod_server': 'www1.cte.fazenda.gov.br', + 'dev_server': 'hom1.cte.fazenda.gov.br', + }, +} + diff --git a/nfelib/cte/client/servers_scraper.py b/nfelib/cte/client/servers_scraper.py new file mode 100644 index 00000000..926a6ffb --- /dev/null +++ b/nfelib/cte/client/servers_scraper.py @@ -0,0 +1,25 @@ +import logging +from pathlib import Path +from typing import Dict, Any +from io import StringIO # Add this import + +import pandas as pd +import requests +from bs4 import BeautifulSoup +from xsdata.formats.dataclass.serializers import PycodeSerializer +from nfelib.utils.servers_scraper import fetch_servers, save_servers + +# Constants +PROD_URL = "https://www.cte.fazenda.gov.br/portal/webServices.aspx" +DEV_URL = "https://hom.cte.fazenda.gov.br/portal/webServices.aspx" +OUTPUT_FILE = Path("nfelib/cte/client/servers.py") + + +def main(): + servers, constants = fetch_servers(PROD_URL, DEV_URL) + if servers: + save_servers(servers, constants, OUTPUT_FILE) + + +if __name__ == "__main__": + main() From b2d639d5a95ed02f300f88184ddd464f49e0ea67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 15:54:05 +0000 Subject: [PATCH 09/38] refactor: reusable wsdl_downloader + generate SOAP bindings option --- nfelib/nfe/client/wsdl_downloader.py | 23 ++++++++++++++++++++--- nfelib/{ => utils}/wsdl_downloader.py | 20 +++++++++++++++++++- 2 files changed, 39 insertions(+), 4 deletions(-) rename nfelib/{ => utils}/wsdl_downloader.py (83%) diff --git a/nfelib/nfe/client/wsdl_downloader.py b/nfelib/nfe/client/wsdl_downloader.py index 9317831c..c9dcd5d5 100644 --- a/nfelib/nfe/client/wsdl_downloader.py +++ b/nfelib/nfe/client/wsdl_downloader.py @@ -1,9 +1,21 @@ import logging -import importlib -from nfelib.wsdl_downloader import download_wsdl_files +import os +import subprocess +import sys +from nfelib.utils.wsdl_downloader import download_wsdl_files -if __name__ == "__main__": + +def main(): logging.basicConfig(level=logging.INFO) + + generate = False + if "--generate" in sys.argv: + generate = True + sys.argv.remove( + "--generate" + ) # Remove the --generate flag to avoid interfering with argparse + + # Call the generic download_wsdl_files method with NFE-specific URLs download_wsdl_files( # NF-e "https://nfe.svrs.rs.gov.br/ws/nfeinutilizacao/nfeinutilizacao4.asmx", @@ -14,4 +26,9 @@ "https://nfe.svrs.rs.gov.br/ws/NfeAutorizacao/NFeAutorizacao4.asmx", "https://nfe.svrs.rs.gov.br/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx", "https://www1.nfe.fazenda.gov.br/NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx", + generate=generate, ) + + +if __name__ == "__main__": + main() diff --git a/nfelib/wsdl_downloader.py b/nfelib/utils/wsdl_downloader.py similarity index 83% rename from nfelib/wsdl_downloader.py rename to nfelib/utils/wsdl_downloader.py index 278e49f5..e2c45154 100644 --- a/nfelib/wsdl_downloader.py +++ b/nfelib/utils/wsdl_downloader.py @@ -1,4 +1,6 @@ import logging +import subprocess +import sys import os from os import environ import urllib3 @@ -20,7 +22,7 @@ } -def download_wsdl_files(*wsdl_urls): +def download_wsdl_files(*wsdl_urls, generate=False): """Download WSDL files for NF-e, CT-e, MDF-e, and BP-e.""" # Access the certificate and password from environment variables @@ -90,3 +92,19 @@ def download_wsdl_files(*wsdl_urls): except Exception as e: _logger.error(f"Failed to download or save WSDL from {url}: {e}") raise + + if generate: + soap_dir = wsdl_dir.replace("wsdl", "soap") + command = [ + "xsdata", + "generate", + "--package", + soap_dir, + wsdl_dir, + ] + try: + subprocess.run(command, check=True) + logging.info("Successfully generated SOAP bindings.") + except subprocess.CalledProcessError as e: + logging.error(f"Failed to generate SOAP bindings: {e}") + sys.exit(1) From e5dd8cf1ff1ebb080bfd2dabe9ee12038a307244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 15:54:56 +0000 Subject: [PATCH 10/38] add NF-e WSDL files --- .../nfe/wsdl/v4_0/cadconsultacadastro4.wsdl | 42 +++++++ nfelib/nfe/wsdl/v4_0/nfeautorizacao4.wsdl | 112 ++++++++++++++++++ nfelib/nfe/wsdl/v4_0/nfeconsulta4.wsdl | 65 ++++++++++ nfelib/nfe/wsdl/v4_0/nfedistribuicaodfe.wsdl | 77 ++++++++++++ nfelib/nfe/wsdl/v4_0/nfeinutilizacao4.wsdl | 65 ++++++++++ nfelib/nfe/wsdl/v4_0/nferetautorizacao4.wsdl | 65 ++++++++++ nfelib/nfe/wsdl/v4_0/nfestatusservico4.wsdl | 65 ++++++++++ nfelib/nfe/wsdl/v4_0/recepcaoevento4.wsdl | 65 ++++++++++ 8 files changed, 556 insertions(+) create mode 100644 nfelib/nfe/wsdl/v4_0/cadconsultacadastro4.wsdl create mode 100644 nfelib/nfe/wsdl/v4_0/nfeautorizacao4.wsdl create mode 100644 nfelib/nfe/wsdl/v4_0/nfeconsulta4.wsdl create mode 100644 nfelib/nfe/wsdl/v4_0/nfedistribuicaodfe.wsdl create mode 100644 nfelib/nfe/wsdl/v4_0/nfeinutilizacao4.wsdl create mode 100644 nfelib/nfe/wsdl/v4_0/nferetautorizacao4.wsdl create mode 100644 nfelib/nfe/wsdl/v4_0/nfestatusservico4.wsdl create mode 100644 nfelib/nfe/wsdl/v4_0/recepcaoevento4.wsdl diff --git a/nfelib/nfe/wsdl/v4_0/cadconsultacadastro4.wsdl b/nfelib/nfe/wsdl/v4_0/cadconsultacadastro4.wsdl new file mode 100644 index 00000000..4e0b05d2 --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/cadconsultacadastro4.wsdl @@ -0,0 +1,42 @@ + + + + The resource cannot be found. + + + + + + +

Server Error in '/WS' Application.

+ +

The resource cannot be found.

+ + + + Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable.  Please review the following URL and make sure that it is spelled correctly. +

+ + Requested URL: /ws/cadconsultacadastro/cadconsultacadastro4.asmx

+ +
+ + + diff --git a/nfelib/nfe/wsdl/v4_0/nfeautorizacao4.wsdl b/nfelib/nfe/wsdl/v4_0/nfeautorizacao4.wsdl new file mode 100644 index 00000000..d258b1cb --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/nfeautorizacao4.wsdl @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/nfe/wsdl/v4_0/nfeconsulta4.wsdl b/nfelib/nfe/wsdl/v4_0/nfeconsulta4.wsdl new file mode 100644 index 00000000..437ad77a --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/nfeconsulta4.wsdl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/nfe/wsdl/v4_0/nfedistribuicaodfe.wsdl b/nfelib/nfe/wsdl/v4_0/nfedistribuicaodfe.wsdl new file mode 100644 index 00000000..12fb79a4 --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/nfedistribuicaodfe.wsdl @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/nfe/wsdl/v4_0/nfeinutilizacao4.wsdl b/nfelib/nfe/wsdl/v4_0/nfeinutilizacao4.wsdl new file mode 100644 index 00000000..02f80812 --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/nfeinutilizacao4.wsdl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/nfe/wsdl/v4_0/nferetautorizacao4.wsdl b/nfelib/nfe/wsdl/v4_0/nferetautorizacao4.wsdl new file mode 100644 index 00000000..c2495c2a --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/nferetautorizacao4.wsdl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/nfe/wsdl/v4_0/nfestatusservico4.wsdl b/nfelib/nfe/wsdl/v4_0/nfestatusservico4.wsdl new file mode 100644 index 00000000..ae9856dc --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/nfestatusservico4.wsdl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/nfe/wsdl/v4_0/recepcaoevento4.wsdl b/nfelib/nfe/wsdl/v4_0/recepcaoevento4.wsdl new file mode 100644 index 00000000..4b21a1ce --- /dev/null +++ b/nfelib/nfe/wsdl/v4_0/recepcaoevento4.wsdl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From dda54bd379afc0d93d74b130c95fc259bc674693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 15:55:16 +0000 Subject: [PATCH 11/38] add NF-e SOAP bindings --- nfelib/nfe/soap/__init__.py | 1 + nfelib/nfe/soap/v4_0/__init__.py | 97 +++++++ nfelib/nfe/soap/v4_0/nfeautorizacao4.py | 315 +++++++++++++++++++++ nfelib/nfe/soap/v4_0/nfeconsulta4.py | 139 +++++++++ nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py | 161 +++++++++++ nfelib/nfe/soap/v4_0/nfeinutilizacao4.py | 135 +++++++++ nfelib/nfe/soap/v4_0/nferetautorizacao4.py | 141 +++++++++ nfelib/nfe/soap/v4_0/nfestatusservico4.py | 139 +++++++++ nfelib/nfe/soap/v4_0/recepcaoevento4.py | 143 ++++++++++ 9 files changed, 1271 insertions(+) create mode 100644 nfelib/nfe/soap/__init__.py create mode 100644 nfelib/nfe/soap/v4_0/__init__.py create mode 100644 nfelib/nfe/soap/v4_0/nfeautorizacao4.py create mode 100644 nfelib/nfe/soap/v4_0/nfeconsulta4.py create mode 100644 nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py create mode 100644 nfelib/nfe/soap/v4_0/nfeinutilizacao4.py create mode 100644 nfelib/nfe/soap/v4_0/nferetautorizacao4.py create mode 100644 nfelib/nfe/soap/v4_0/nfestatusservico4.py create mode 100644 nfelib/nfe/soap/v4_0/recepcaoevento4.py diff --git a/nfelib/nfe/soap/__init__.py b/nfelib/nfe/soap/__init__.py new file mode 100644 index 00000000..b2a4ba59 --- /dev/null +++ b/nfelib/nfe/soap/__init__.py @@ -0,0 +1 @@ +# nothing here diff --git a/nfelib/nfe/soap/v4_0/__init__.py b/nfelib/nfe/soap/v4_0/__init__.py new file mode 100644 index 00000000..828da2bf --- /dev/null +++ b/nfelib/nfe/soap/v4_0/__init__.py @@ -0,0 +1,97 @@ +from nfelib.nfe.soap.v4_0.nfeautorizacao4 import ( + NfeAutorizacao4SoapNfeAutorizacaoLote, + NfeAutorizacao4SoapNfeAutorizacaoLoteZip, + NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput, + NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput, + NfeAutorizacao4SoapNfeAutorizacaoLoteInput, + NfeAutorizacao4SoapNfeAutorizacaoLoteOutput, + NfeDadosMsg as Nfeautorizacao4NfeDadosMsg, + NfeDadosMsgZip, + NfeMonitoria, + NfeResultMsg as Nfeautorizacao4NfeResultMsg, +) +from nfelib.nfe.soap.v4_0.nfeconsulta4 import ( + NfeConsultaProtocolo4SoapNfeConsultaNf, + NfeConsultaProtocolo4SoapNfeConsultaNfInput, + NfeConsultaProtocolo4SoapNfeConsultaNfOutput, + NfeDadosMsg as Nfeconsulta4NfeDadosMsg, + NfeResultMsg as Nfeconsulta4NfeResultMsg, +) +from nfelib.nfe.soap.v4_0.nfedistribuicaodfe import ( + NfeDistribuicaoDfeSoapNfeDistDfeInteresse, + NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput, + NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput, + NfeDistDfeInteresse, + NfeDistDfeInteresseResponse, +) +from nfelib.nfe.soap.v4_0.nfeinutilizacao4 import ( + NfeInutilizacao4SoapNfeInutilizacaoNf, + NfeInutilizacao4SoapNfeInutilizacaoNfInput, + NfeInutilizacao4SoapNfeInutilizacaoNfOutput, + NfeDadosMsg as Nfeinutilizacao4NfeDadosMsg, + NfeResultMsg as Nfeinutilizacao4NfeResultMsg, +) +from nfelib.nfe.soap.v4_0.nferetautorizacao4 import ( + NfeRetAutorizacao4SoapNfeRetAutorizacaoLote, + NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput, + NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput, + NfeDadosMsg as Nferetautorizacao4NfeDadosMsg, + NfeResultMsg as Nferetautorizacao4NfeResultMsg, +) +from nfelib.nfe.soap.v4_0.nfestatusservico4 import ( + NfeStatusServico4SoapNfeStatusServicoNf, + NfeStatusServico4SoapNfeStatusServicoNfInput, + NfeStatusServico4SoapNfeStatusServicoNfOutput, + NfeDadosMsg as Nfestatusservico4NfeDadosMsg, + NfeResultMsg as Nfestatusservico4NfeResultMsg, +) +from nfelib.nfe.soap.v4_0.recepcaoevento4 import ( + NfeRecepcaoEvento4SoapNfeRecepcaoEvento, + NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput, + NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput, + NfeDadosMsg as Recepcaoevento4NfeDadosMsg, + NfeResultMsg as Recepcaoevento4NfeResultMsg, +) + +__all__ = [ + "NfeAutorizacao4SoapNfeAutorizacaoLote", + "NfeAutorizacao4SoapNfeAutorizacaoLoteZip", + "NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput", + "NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput", + "NfeAutorizacao4SoapNfeAutorizacaoLoteInput", + "NfeAutorizacao4SoapNfeAutorizacaoLoteOutput", + "Nfeautorizacao4NfeDadosMsg", + "NfeDadosMsgZip", + "NfeMonitoria", + "Nfeautorizacao4NfeResultMsg", + "NfeConsultaProtocolo4SoapNfeConsultaNf", + "NfeConsultaProtocolo4SoapNfeConsultaNfInput", + "NfeConsultaProtocolo4SoapNfeConsultaNfOutput", + "Nfeconsulta4NfeDadosMsg", + "Nfeconsulta4NfeResultMsg", + "NfeDistribuicaoDfeSoapNfeDistDfeInteresse", + "NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput", + "NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput", + "NfeDistDfeInteresse", + "NfeDistDfeInteresseResponse", + "NfeInutilizacao4SoapNfeInutilizacaoNf", + "NfeInutilizacao4SoapNfeInutilizacaoNfInput", + "NfeInutilizacao4SoapNfeInutilizacaoNfOutput", + "Nfeinutilizacao4NfeDadosMsg", + "Nfeinutilizacao4NfeResultMsg", + "NfeRetAutorizacao4SoapNfeRetAutorizacaoLote", + "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput", + "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput", + "Nferetautorizacao4NfeDadosMsg", + "Nferetautorizacao4NfeResultMsg", + "NfeStatusServico4SoapNfeStatusServicoNf", + "NfeStatusServico4SoapNfeStatusServicoNfInput", + "NfeStatusServico4SoapNfeStatusServicoNfOutput", + "Nfestatusservico4NfeDadosMsg", + "Nfestatusservico4NfeResultMsg", + "NfeRecepcaoEvento4SoapNfeRecepcaoEvento", + "NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput", + "NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput", + "Recepcaoevento4NfeDadosMsg", + "Recepcaoevento4NfeResultMsg", +] diff --git a/nfelib/nfe/soap/v4_0/nfeautorizacao4.py b/nfelib/nfe/soap/v4_0/nfeautorizacao4.py new file mode 100644 index 00000000..f471d1f9 --- /dev/null +++ b/nfelib/nfe/soap/v4_0/nfeautorizacao4.py @@ -0,0 +1,315 @@ +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import Dict, List, Optional + + +@dataclass +class NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeDadosMsgZip: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", + }, + ) + + +@dataclass +class NfeDadosMsg(CommonMixin): + class Meta: + name = "nfeDadosMsg" + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeDadosMsgZip(CommonMixin): + class Meta: + name = "nfeDadosMsgZip" + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" + + value: str = field( + default="", + metadata={ + "required": True, + }, + ) + + +@dataclass +class NfeMonitoria(CommonMixin): + class Meta: + name = "nfeMonitoria" + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" + + nomeServidor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + dhServidor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + any_attributes: Dict[str, str] = field( + default_factory=dict, + metadata={ + "type": "Attributes", + "namespace": "##any", + }, + ) + + +@dataclass +class NfeResultMsg(CommonMixin): + class Meta: + name = "nfeResultMsg" + nillable = True + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + Header: Optional[ + "NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput.Header" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + nfeResultMsg: Optional[NfeResultMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", + "nillable": True, + }, + ) + Fault: Optional[ + "NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + @dataclass + class Header(CommonMixin): + nfeMonitoria: Optional[NfeMonitoria] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", + }, + ) + + +@dataclass +class NfeAutorizacao4SoapNfeAutorizacaoLoteInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + nfeDadosMsg: Optional[NfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", + }, + ) + + +@dataclass +class NfeAutorizacao4SoapNfeAutorizacaoLoteOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + Header: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Header"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeResultMsg: Optional[NfeResultMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", + "nillable": True, + }, + ) + Fault: Optional[ + "NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + @dataclass + class Header(CommonMixin): + nfeMonitoria: Optional[NfeMonitoria] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", + }, + ) + + +class NfeAutorizacao4SoapNfeAutorizacaoLote: + style = "document" + location = ( + "https://nfe.svrs.rs.gov.br/ws/NfeAutorizacao/NFeAutorizacao4.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4/nfeAutorizacaoLote" + input = NfeAutorizacao4SoapNfeAutorizacaoLoteInput + output = NfeAutorizacao4SoapNfeAutorizacaoLoteOutput + + +class NfeAutorizacao4SoapNfeAutorizacaoLoteZip: + style = "document" + location = ( + "https://nfe.svrs.rs.gov.br/ws/NfeAutorizacao/NFeAutorizacao4.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4/nfeAutorizacaoLoteZip" + input = NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput + output = NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput diff --git a/nfelib/nfe/soap/v4_0/nfeconsulta4.py b/nfelib/nfe/soap/v4_0/nfeconsulta4.py new file mode 100644 index 00000000..b4a90492 --- /dev/null +++ b/nfelib/nfe/soap/v4_0/nfeconsulta4.py @@ -0,0 +1,139 @@ +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + + +@dataclass +class NfeDadosMsg(CommonMixin): + class Meta: + name = "nfeDadosMsg" + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeResultMsg(CommonMixin): + class Meta: + name = "nfeResultMsg" + nillable = True + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeConsultaProtocolo4SoapNfeConsultaNfInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeConsultaProtocolo4SoapNfeConsultaNfInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + nfeDadosMsg: Optional[NfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4", + }, + ) + + +@dataclass +class NfeConsultaProtocolo4SoapNfeConsultaNfOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeConsultaProtocolo4SoapNfeConsultaNfOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeResultMsg: Optional[NfeResultMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4", + "nillable": True, + }, + ) + Fault: Optional[ + "NfeConsultaProtocolo4SoapNfeConsultaNfOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class NfeConsultaProtocolo4SoapNfeConsultaNf: + style = "document" + location = "https://nfe.svrs.rs.gov.br/ws/NfeConsulta/NfeConsulta4.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4/nfeConsultaNF" + input = NfeConsultaProtocolo4SoapNfeConsultaNfInput + output = NfeConsultaProtocolo4SoapNfeConsultaNfOutput diff --git a/nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py b/nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py new file mode 100644 index 00000000..ed75b1be --- /dev/null +++ b/nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py @@ -0,0 +1,161 @@ +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + + +@dataclass +class NfeDistDfeInteresse(CommonMixin): + class Meta: + name = "nfeDistDFeInteresse" + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe" + ) + + nfeDadosMsg: Optional["NfeDistDfeInteresse.NfeDadosMsg"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class NfeDadosMsg(CommonMixin): + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeDistDfeInteresseResponse(CommonMixin): + class Meta: + name = "nfeDistDFeInteresseResponse" + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe" + ) + + nfeDistDFeInteresseResult: Optional[ + "NfeDistDfeInteresseResponse.NfeDistDfeInteresseResult" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class NfeDistDfeInteresseResult(CommonMixin): + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeDistDFeInteresse: Optional[NfeDistDfeInteresse] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe", + }, + ) + + +@dataclass +class NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeDistDFeInteresseResponse: Optional[NfeDistDfeInteresseResponse] = ( + field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe", + }, + ) + ) + Fault: Optional[ + "NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class NfeDistribuicaoDfeSoapNfeDistDfeInteresse: + style = "document" + location = "https://www1.nfe.fazenda.gov.br/NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe/nfeDistDFeInteresse" + input = NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput + output = NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput diff --git a/nfelib/nfe/soap/v4_0/nfeinutilizacao4.py b/nfelib/nfe/soap/v4_0/nfeinutilizacao4.py new file mode 100644 index 00000000..d996ee95 --- /dev/null +++ b/nfelib/nfe/soap/v4_0/nfeinutilizacao4.py @@ -0,0 +1,135 @@ +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + + +@dataclass +class NfeDadosMsg(CommonMixin): + class Meta: + name = "nfeDadosMsg" + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeInutilizacao4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeResultMsg(CommonMixin): + class Meta: + name = "nfeResultMsg" + nillable = True + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeInutilizacao4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeInutilizacao4SoapNfeInutilizacaoNfInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeInutilizacao4SoapNfeInutilizacaoNfInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + nfeDadosMsg: Optional[NfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeInutilizacao4", + }, + ) + + +@dataclass +class NfeInutilizacao4SoapNfeInutilizacaoNfOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeInutilizacao4SoapNfeInutilizacaoNfOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + nfeResultMsg: Optional[NfeResultMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeInutilizacao4", + "nillable": True, + }, + ) + Fault: Optional[ + "NfeInutilizacao4SoapNfeInutilizacaoNfOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class NfeInutilizacao4SoapNfeInutilizacaoNf: + style = "document" + location = ( + "https://nfe.svrs.rs.gov.br/ws/nfeinutilizacao/nfeinutilizacao4.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeInutilizacao4/nfeInutilizacaoNF" + input = NfeInutilizacao4SoapNfeInutilizacaoNfInput + output = NfeInutilizacao4SoapNfeInutilizacaoNfOutput diff --git a/nfelib/nfe/soap/v4_0/nferetautorizacao4.py b/nfelib/nfe/soap/v4_0/nferetautorizacao4.py new file mode 100644 index 00000000..60231752 --- /dev/null +++ b/nfelib/nfe/soap/v4_0/nferetautorizacao4.py @@ -0,0 +1,141 @@ +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + + +@dataclass +class NfeDadosMsg(CommonMixin): + class Meta: + name = "nfeDadosMsg" + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRetAutorizacao4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeResultMsg(CommonMixin): + class Meta: + name = "nfeResultMsg" + nillable = True + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRetAutorizacao4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeDadosMsg: Optional[NfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRetAutorizacao4", + }, + ) + + +@dataclass +class NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional[ + "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput.Body" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + nfeResultMsg: Optional[NfeResultMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRetAutorizacao4", + "nillable": True, + }, + ) + Fault: Optional[ + "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class NfeRetAutorizacao4SoapNfeRetAutorizacaoLote: + style = "document" + location = "https://nfe.svrs.rs.gov.br/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRetAutorizacao4/nfeRetAutorizacaoLote" + input = NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput + output = NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput diff --git a/nfelib/nfe/soap/v4_0/nfestatusservico4.py b/nfelib/nfe/soap/v4_0/nfestatusservico4.py new file mode 100644 index 00000000..7748696c --- /dev/null +++ b/nfelib/nfe/soap/v4_0/nfestatusservico4.py @@ -0,0 +1,139 @@ +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + + +@dataclass +class NfeDadosMsg(CommonMixin): + class Meta: + name = "nfeDadosMsg" + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeResultMsg(CommonMixin): + class Meta: + name = "nfeResultMsg" + nillable = True + namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeStatusServico4SoapNfeStatusServicoNfInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeStatusServico4SoapNfeStatusServicoNfInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeDadosMsg: Optional[NfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4", + }, + ) + + +@dataclass +class NfeStatusServico4SoapNfeStatusServicoNfOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeStatusServico4SoapNfeStatusServicoNfOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeResultMsg: Optional[NfeResultMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4", + "nillable": True, + }, + ) + Fault: Optional[ + "NfeStatusServico4SoapNfeStatusServicoNfOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class NfeStatusServico4SoapNfeStatusServicoNf: + style = "document" + location = ( + "https://nfe.svrs.rs.gov.br/ws/NfeStatusServico/NfeStatusServico4.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4/nfeStatusServicoNF" + input = NfeStatusServico4SoapNfeStatusServicoNfInput + output = NfeStatusServico4SoapNfeStatusServicoNfOutput diff --git a/nfelib/nfe/soap/v4_0/recepcaoevento4.py b/nfelib/nfe/soap/v4_0/recepcaoevento4.py new file mode 100644 index 00000000..d4bf1674 --- /dev/null +++ b/nfelib/nfe/soap/v4_0/recepcaoevento4.py @@ -0,0 +1,143 @@ +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + + +@dataclass +class NfeDadosMsg(CommonMixin): + class Meta: + name = "nfeDadosMsg" + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeResultMsg(CommonMixin): + class Meta: + name = "nfeResultMsg" + nillable = True + namespace = ( + "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeDadosMsg: Optional[NfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4", + }, + ) + + +@dataclass +class NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + nfeResultMsg: Optional[NfeResultMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4", + "nillable": True, + }, + ) + Fault: Optional[ + "NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class NfeRecepcaoEvento4SoapNfeRecepcaoEvento: + style = "document" + location = ( + "https://nfe.svrs.rs.gov.br/ws/recepcaoevento/recepcaoevento4.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4/nfeRecepcaoEvento" + input = NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput + output = NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput From a3a0dabdf03de806814349e1ee2e4b5b6a477ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 22 Mar 2025 15:57:51 +0000 Subject: [PATCH 12/38] add NF-e initial SOAP client --- nfelib/nfe/client/v4_0.py | 452 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 nfelib/nfe/client/v4_0.py diff --git a/nfelib/nfe/client/v4_0.py b/nfelib/nfe/client/v4_0.py new file mode 100644 index 00000000..aa5f902d --- /dev/null +++ b/nfelib/nfe/client/v4_0.py @@ -0,0 +1,452 @@ +# Copyright (C) 2024 Raphaël Valyi - Akretion + +import time +from datetime import date, datetime +from os import environ +from typing import Any, Optional, Type + +from brazil_fiscal_client.fiscal_client import FiscalClient, Tamb, TcodUfIbge + +from nfelib.nfe.bindings.v4_0.cons_reci_nfe_v4_00 import ConsReciNfe +from nfelib.nfe.bindings.v4_0.cons_sit_nfe_v4_00 import ConsSitNfe +from nfelib.nfe.bindings.v4_0.cons_stat_serv_v4_00 import ConsStatServ +from nfelib.nfe.bindings.v4_0.envi_nfe_v4_00 import EnviNfe +from nfelib.nfe.bindings.v4_0.inut_nfe_v4_00 import InutNfe +from nfelib.nfe.bindings.v4_0.leiaute_inut_nfe_v4_00 import TinutNfe +from nfelib.nfe.bindings.v4_0.leiaute_nfe_v4_00 import Tnfe +from nfelib.nfe.bindings.v4_0.ret_cons_reci_nfe_v4_00 import RetConsReciNfe +from nfelib.nfe.bindings.v4_0.ret_cons_sit_nfe_v4_00 import RetConsSitNfe +from nfelib.nfe.bindings.v4_0.ret_cons_stat_serv_v4_00 import RetConsStatServ +from nfelib.nfe.bindings.v4_0.ret_envi_nfe_v4_00 import RetEnviNfe +from nfelib.nfe.bindings.v4_0.ret_inut_nfe_v4_00 import RetInutNfe +from nfelib.nfe_evento_cancel.bindings.v1_0.evento_canc_nfe_v1_00 import ( + Tevento as TeventoCancel, +) +from nfelib.nfe_evento_cancel.bindings.v1_0.leiaute_evento_canc_nfe_v1_00 import ( + TenvEvento, + TretEnvEvento, +) +from nfelib.nfe_evento_cce.bindings.v1_0.leiaute_cce_v1_00 import Tevento as TeventoCCe + +from nfelib.nfe.soap.v4_0.nfeautorizacao4 import ( + NfeAutorizacao4SoapNfeAutorizacaoLote, +) +from nfelib.nfe.soap.v4_0.nfeconsulta4 import ( + NfeConsultaProtocolo4SoapNfeConsultaNf, +) + +# TODO distribuição: +from nfelib.nfe.soap.v4_0.nfedistribuicaodfe import ( + NfeDistribuicaoDfeSoapNfeDistDfeInteresse, +) +from nfelib.nfe.soap.v4_0.nfeinutilizacao4 import ( + NfeInutilizacao4SoapNfeInutilizacaoNf, +) +from nfelib.nfe.soap.v4_0.nferetautorizacao4 import ( + NfeRetAutorizacao4SoapNfeRetAutorizacaoLote, +) +from nfelib.nfe.soap.v4_0.nfestatusservico4 import ( + NfeStatusServico4SoapNfeStatusServicoNf, +) +from nfelib.nfe.soap.v4_0.recepcaoevento4 import ( + NfeRecepcaoEvento4SoapNfeRecepcaoEvento, +) +from nfelib.nfe_evento_cancel.bindings.v1_0.evento_canc_nfe_v1_00 import ( + Tevento as TeventoCancel, +) +from nfelib.nfe_evento_cancel.bindings.v1_0.leiaute_evento_canc_nfe_v1_00 import ( + TenvEvento, + TretEnvEvento, +) +from nfelib.nfe_evento_cce.bindings.v1_0.leiaute_cce_v1_00 import Tevento as TeventoCCe + +# TODO import file generated by test_generate_servers.py instead +SERVERS_NFE = { + "AM": {}, # ... + "SVRS": { + "prod_server": "nfe.svrs.rs.gov.br", + "dev_server": "nfe-homologacao.svrs.rs.gov.br", + "NfeInutilizacao": "/ws/nfeinutilizacao/nfeinutilizacao4.asmx", + "NfeConsultaProtocolo": "/ws/NfeConsulta/NfeConsulta4.asmx", + "NfeStatusServico": "/ws/NfeStatusServico/NfeStatusServico4.asmx", + "NfeConsultaCadastro": "/ws/cadconsultacadastro/cadconsultacadastro4.asmx", + "RecepcaoEvento": "/ws/recepcaoevento/recepcaoevento4.asmx", + "NFeAutorizacao": "/ws/NfeAutorizacao/NFeAutorizacao4.asmx", + "NFeRetAutorizacao": "/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx", + }, # ... +} + + +UF_TO_SERVER = { # NOTE this is fixed compared to erpbrasil.edoc + "11": "SVRS", + "12": "SVRS", + "13": "AM", + "14": "SVRS", + "15": "BA", + "16": "SVRS", + "17": "SVRS", + "21": "SVRS", + "22": "GO", + "23": "SVAN", + "24": "SP", + "25": "MT", + "26": "MS", + "27": "MG", + "28": "PE", + "29": "SVRS", + "31": "SVRS", + "32": "SVRS", + "33": "SVRS", + "35": "PR", + "41": "SVRS", + "42": "SVRS", + "43": "SVRS", + "50": "SVRS", + "51": "SVRS", + "52": "RS", + "53": "SVRS", + "AN": "AN", +} + + +class NfeClient(FiscalClient): + """A façade for the NFe SOAP webserices.""" + + def __init__(self, **kwargs: Any): + super().__init__( + service="nfe", + versao="4.00", + # server=self._get_server("nfe", kwargs.get("uf")), + **kwargs, + ) + + def _get_location(self, action_class: Type) -> str: + action_class_to_action = { + NfeInutilizacao4SoapNfeInutilizacaoNf: "NfeInutilizacao", + NfeConsultaProtocolo4SoapNfeConsultaNf: "NfeConsultaProtocolo", + NfeStatusServico4SoapNfeStatusServicoNf: "NfeStatusServico", + # NfeConsultaCadastro: "NfeConsultaCadastro", # TODO + NfeRecepcaoEvento4SoapNfeRecepcaoEvento: "RecepcaoEvento", + NfeAutorizacao4SoapNfeAutorizacaoLote: "NFeAutorizacao", + NfeRetAutorizacao4SoapNfeRetAutorizacaoLote: "NFeRetAutorizacao", + NfeDistribuicaoDfeSoapNfeDistDfeInteresse: "NFeDistribuicaoDFe", + } + action_key = action_class_to_action[action_class] + # FIXME if action_key == "NfeConsultaCadastro" and state: AC, ES, RN, PB, SC + # then use SVRS server as per the doc at the top of + # https://www.nfe.fazenda.gov.br/portal/webServices.aspx?tipoConteudo=OUC/YVNWZfo= + # TODO Contingência + server_key = UF_TO_SERVER[self.uf] + server_data = SERVERS_NFE[server_key] + if self.ambiente == Tamb.PROD: + server = server_data["prod_server"] + else: + server = server_data["dev_server"] + path = server_data[action_key] + return "https://" + server + path + + def send( + self, + action_class: Type, + obj: Any, + placeholder_exp: Optional[str] = None, + placeholder_content: Optional[str] = None, + **kwargs: Any, + ) -> Any: + """Build and send a request for the input object. + + Args: + action_class: Type generated with xsdata for the SOAP wsdl + obj: The request model instance or a pure dictionary + placeholder_content: a string content to be injected in the + payload. Used for signed content to avoid signature issues. + placeholder_exp: placeholder where to inject placeholder_content + the response into the right class. Usually useless if the + proper return type has been imported already. + kwargs: keyword arguments + + Returns: + The response model instance. + """ + location = self._get_location(action_class) + wrapped_obj = {"Body": {"nfeDadosMsg": {"content": [obj]}}} + response = super().send( + action_class, + location, + wrapped_obj, + placeholder_exp=placeholder_exp, + placeholder_content=placeholder_content, + **kwargs, + ) + return response.body.nfeResultMsg.content[0] + + ###################################### + # Webservices + ###################################### + + # OK 100% + def status_servico(self, xServ: str = "STATUS") -> RetConsStatServ: + return self.send( + NfeStatusServico4SoapNfeStatusServicoNf, + ConsStatServ( + tpAmb=self.ambiente, + cUF=self.uf, + xServ=xServ, + versao=self.versao, + ), + ) + + # OK 100% + def consulta_documento(self, chave: str, xServ: str = "CONSULTAR") -> RetConsSitNfe: + return self.send( + NfeConsultaProtocolo4SoapNfeConsultaNf, + ConsSitNfe( + versao=self.versao, + tpAmb=self.ambiente, + xServ=xServ, + chNFe=chave, + ), + ) + + # NOTE: I changed the signature from erpbrasil.edoc to support a list of NFe's + # OK 100% + # TODO call autoriza_documento that returns the protocol without waiting + def envia_documento( + self, lista_nfes: list, id_lote=None, ind_sinc="0" + ) -> RetEnviNfe: + # TODO see if NFe's should be signed + # for nfe in nfe_list... + return self.send( + NfeAutorizacao4SoapNfeAutorizacaoLote, + EnviNfe( + idLote=id_lote or datetime.now().strftime("%Y%m%d%H%M%S"), + versao=self.versao, + indSinc=ind_sinc, + # we pass an empty placeholder_exp for the NFe to avoid an extra + # XML parsing/serialization and possibly screwing the signature. + NFe=[Tnfe()], + ), + placeholder_exp="", + placeholder_content="".join(lista_nfes), + ) + + # NOTE erpbrasil.edoc coupling with TinutNfe seems bad, better take signed xml? + # in wmixvideo inutilizaNota(final int anoInutilizacaoNumeracao, final String cnpjEmitente, final String serie, final String numeroInicial, final String numeroFinal, final String justificativa, final DFModelo modelo) + # and inutilizaNotaAssinada(final eventoAssinadoXml, modelo) + def envia_inutilizacao(self, evento: InutNfe.InfInut) -> RetInutNfe: + # NOTE: sure we don't take a signed xml input? + signed_xml = evento.to_xml( + pkcs12_data=self.pkcs12_data, + pkcs12_password=self.pkcs12_password, + doc_id=evento.infInut.Id, + ) + return self.send( + NfeInutilizacao4SoapNfeInutilizacaoNf, + InutNfe(), + # versao=self.versao, + # infInut=InutNfe.InfInut(), # placeholder for signed xml + # signature=None, + # ), + placeholder_exp="", + placeholder_content=signed_xml, + ) + + # OK 100% + def consulta_recibo( + self, numero: str = "", proc_envio: RetEnviNfe = None + ) -> RetConsReciNfe: + if proc_envio: + numero = proc_envio.infRec.nRec + + if not numero: + raise ValueError("Sem numero para consultar!") + + return self.send( + NfeRetAutorizacao4SoapNfeRetAutorizacaoLote, + ConsReciNfe( + versao=self.versao, + tpAmb=self.ambiente, + nRec=numero, + ), + ) + + # NOTE bad name from erpbrasil.edoc + # in wmixvideo cancelaNota(chaveDeAcessoDaNota, protocoloDaNota, motivoCancelaamento) + # and cancelaNotaAssinada(chave, eventoAssinadoXml) + # ver tb cancelaNotaPorSubstituicao permitido para a NFCe + def enviar_lote_evento(self, lista_eventos, numero_lote: str = "") -> TretEnvEvento: + if not numero_lote: + numero_lote = datetime.now().strftime("%Y%m%d%H%M%S") + + signed_events_xml = "" + for inf_evento in lista_eventos: + evento = TeventoCancel( # seems bad to use specific event in generic method + versao="1.00", + infEvento=inf_evento, + ) + signed_xml = evento.to_xml( + pkcs12_data=self.pkcs12_data, + pkcs12_password=self.pkcs12_password, + doc_id=evento.infEvento.Id, + ) + signed_events_xml += signed_xml # TODO ensure this works! + return self.send( + NfeRecepcaoEvento4SoapNfeRecepcaoEvento, + TenvEvento( + versao="1.00", + idLote=numero_lote, + evento=[TeventoCancel()], # placeholder + ), + placeholder_exp='', # "", + placeholder_content=signed_events_xml, + ) + + # TODO + # consultaCadastro(final String cnpj, final DFUnidadeFederativa uf) + # Realiza a consulta de cadastro de pessoa juridica com inscricao estadual + + ###################################### + # Binding façades + ###################################### + + # OK 100% + def cancela_documento( + self, + chave: str, + protocolo_autorizacao: str, + justificativa: str, + data_hora_evento=False, + ): + """ + Binding details in: + nfelib/nfe_evento_cancel/bindings/v1_0/leiaute_evento_canc_nfe_v1_00.py + """ + tipo_evento = "110111" + sequencia = "1" + return TeventoCancel.InfEvento( + Id="ID" + tipo_evento + chave + sequencia.zfill(2), + cOrgao=self.uf, + tpAmb=self.ambiente, + CNPJ=chave[6:20], + # CPF TODO + chNFe=chave, + dhEvento=data_hora_evento or self._timestamp(), + tpEvento="110111", + nSeqEvento="1", + verEvento="1.00", + detEvento=TeventoCancel.InfEvento.DetEvento( + versao="1.00", + descEvento="Cancelamento", + nProt=protocolo_autorizacao, + xJust=justificativa, + ), + ) + + # OK 100% + def carta_correcao( + self, chave: str, sequencia: str, justificativa: str, data_hora_evento: str = "" + ): + """ + Binding details: + nfelib/nfe_evento_cancel/bindings/v1_0/leiaute_evento_canc_nfe_v1_00.py + """ + return TeventoCCe.InfEvento( + Id="ID" + tipo_evento + chave + sequencia.zfill(2), + cOrgao=self.uf, + tpAmb=self.ambiente, + CNPJ=chave[6:20], + CPF=None, # TODO + chNFe=chave, + dhEvento=data_hora_evento or self._timestamp(), + tpEvento=tipo_evento, + nSeqEvento=sequencia, + verEvento="1.00", + detEvento=TeventoCCe.InfEvento.DetEvento( + versao="1.00", + descEvento="Carta de Correcao", + xCorrecao=justificativa, + xCondUso=TEXTO_CARTA_CORRECAO, + ), + ) + + # OK 100% + def inutilizacao( + self, + cnpj: str, + mod: str, + serie: str, + num_ini: str, + num_fin: str, + justificativa: str, + ) -> TinutNfe: + """ + Binding details in: + nfelib/nfe/bindings/v4_0/leiaute_inut_nfe_v4_00.py + """ + year = str(date.today().year)[2:] + return InutNfe( + infInut=InutNfe.InfInut( + Id="ID" + + self.uf + + year + + cnpj + + mod + + serie.zfill(3) + + str(num_ini).zfill(9) + + str(num_fin).zfill(9), + tpAmb=self.ambiente, + xServ="INUTILIZAR", + cUF=self.uf, + ano=year, + CNPJ=cnpj, + mod=mod, + serie=serie, + nNFIni=str(num_ini), + nNFFin=str(num_fin), + xJust=justificativa, + ), + versao=self.versao, + ) + + ###################################### + # Misc + ###################################### + + def _aguarda_tempo_medio(self, proc_envio: EnviNfe): + time.sleep(float(proc_envio.infRec.tMed) * 1.3) + + ###################################### + # DF-e + ###################################### + + def consultar_distribuicao( + self, cnpj_cpf, ultimo_nsu=False, nsu_especifico=False, chave=False + ): + return self.send( + NfeDistribuicaoDfeSoapNfeDistDfeInteresse, + # TODO + ) + + # def monta_processo(self, edoc, proc_envio, proc_recibo): + # nfe = proc_envio.envio_raiz.find('{' + self._namespace + '}NFe') + # protocolos = proc_recibo.resposta.protNFe + # if nfe and protocolos: + # if type(protocolos) != list: + # protocolos = [protocolos] + # for protocolo in protocolos: + # nfe_proc = retEnviNFe.TNfeProc( + # versao=self.versao, + # protNFe=protocolo, + # ) + # nfe_proc.original_tagname_ = 'nfeProc' + # xml_file, nfe_proc = self._generateds_to_string_etree(nfe_proc) + # prot_nfe = nfe_proc.find('{' + self._namespace + '}protNFe') + # prot_nfe.addprevious(nfe) + # proc_recibo.processo = nfe_proc + # proc_recibo.processo_xml = \ + # self._generateds_to_string_etree(nfe_proc)[0] + # proc_recibo.protocolo = protocolo + # return True + + def consultar_cadastro(self, uf, cnpj=None, cpf=None, ie=None): + pass From 156862aefd83c253cb03f2a492952b4ef669471d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 01:55:14 +0000 Subject: [PATCH 13/38] scoped files with v4_0; cleanup --- nfelib/nfe/client/{v4_0.py => v4_0/__init__.py} | 0 nfelib/nfe/client/{ => v4_0}/servers.py | 0 nfelib/nfe/client/{ => v4_0}/servers_scraper.py | 7 ------- nfelib/nfe/client/{ => v4_0}/wsdl_downloader.py | 0 4 files changed, 7 deletions(-) rename nfelib/nfe/client/{v4_0.py => v4_0/__init__.py} (100%) rename nfelib/nfe/client/{ => v4_0}/servers.py (100%) rename nfelib/nfe/client/{ => v4_0}/servers_scraper.py (68%) rename nfelib/nfe/client/{ => v4_0}/wsdl_downloader.py (100%) diff --git a/nfelib/nfe/client/v4_0.py b/nfelib/nfe/client/v4_0/__init__.py similarity index 100% rename from nfelib/nfe/client/v4_0.py rename to nfelib/nfe/client/v4_0/__init__.py diff --git a/nfelib/nfe/client/servers.py b/nfelib/nfe/client/v4_0/servers.py similarity index 100% rename from nfelib/nfe/client/servers.py rename to nfelib/nfe/client/v4_0/servers.py diff --git a/nfelib/nfe/client/servers_scraper.py b/nfelib/nfe/client/v4_0/servers_scraper.py similarity index 68% rename from nfelib/nfe/client/servers_scraper.py rename to nfelib/nfe/client/v4_0/servers_scraper.py index 5558db47..9f4c61cf 100644 --- a/nfelib/nfe/client/servers_scraper.py +++ b/nfelib/nfe/client/v4_0/servers_scraper.py @@ -1,12 +1,5 @@ -import logging from pathlib import Path -from typing import Dict, Any -from io import StringIO # Add this import -import pandas as pd -import requests -from bs4 import BeautifulSoup -from xsdata.formats.dataclass.serializers import PycodeSerializer from nfelib.utils.servers_scraper import fetch_servers, save_servers # Constants diff --git a/nfelib/nfe/client/wsdl_downloader.py b/nfelib/nfe/client/v4_0/wsdl_downloader.py similarity index 100% rename from nfelib/nfe/client/wsdl_downloader.py rename to nfelib/nfe/client/v4_0/wsdl_downloader.py From 786db8e2367fd74671dd468c010bd4c6d4257c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 05:34:35 +0000 Subject: [PATCH 14/38] clean up servers scraper --- nfelib/utils/servers_scraper.py | 8 +++++--- tests/nfe/test_servers.py | 15 ++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/nfelib/utils/servers_scraper.py b/nfelib/utils/servers_scraper.py index d93edc09..798d9003 100644 --- a/nfelib/utils/servers_scraper.py +++ b/nfelib/utils/servers_scraper.py @@ -1,7 +1,9 @@ +# Copyright (C) 2024 Raphaël Valyi - Akretion + import logging from pathlib import Path -from typing import Dict, Any -from io import StringIO # Add this import +from typing import Dict, Any, Tuple +from io import StringIO import pandas as pd import requests @@ -17,7 +19,7 @@ logger = logging.getLogger(__name__) -def fetch_servers(prod_url: str, dev_url: str) -> Dict[str, Any]: +def fetch_servers(prod_url: str, dev_url: str) -> Tuple[Dict[str, Any], Dict[str, Any]]: """Fetches the NFe server list from the webpage using pandas and BeautifulSoup.""" servers = {} constants = {} # To store dynamically generated constants diff --git a/tests/nfe/test_servers.py b/tests/nfe/test_servers.py index 121d2761..8f30b753 100644 --- a/tests/nfe/test_servers.py +++ b/tests/nfe/test_servers.py @@ -1,16 +1,17 @@ -import subprocess from pathlib import Path +from nfelib.nfe.client.v4_0.servers_scraper import main + +OUTPUT_FILE = Path("nfelib/nfe/client/v4_0/servers.py") -OUTPUT_FILE = Path("nfelib/nfe/client/servers.py") def read_current_servers(): return OUTPUT_FILE.read_text(encoding="utf-8") if OUTPUT_FILE.exists() else "" + def test_scraper(): old_content = read_current_servers() - - subprocess.run(["python", "nfelib/nfe/client/servers_scraper.py"], check=True) - + main() new_content = read_current_servers() - assert new_content == old_content, "Server list has changed. Review and commit the new file." - + assert ( + new_content == old_content + ), "Server list has changed. Review and commit the new file." From 99af2f78df1ac8f52acac33daaed72a1363e2ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 05:37:42 +0000 Subject: [PATCH 15/38] fixed SOAP bindings generation --- nfelib/utils/wsdl_downloader.py | 42 +++++++++++++++++---------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/nfelib/utils/wsdl_downloader.py b/nfelib/utils/wsdl_downloader.py index e2c45154..4bc74aa1 100644 --- a/nfelib/utils/wsdl_downloader.py +++ b/nfelib/utils/wsdl_downloader.py @@ -1,10 +1,11 @@ +# Copyright (C) 2024 Raphaël Valyi - Akretion + import logging -import subprocess -import sys import os +import subprocess from os import environ -import urllib3 +import urllib3 from requests import Session from requests_pkcs12 import Pkcs12Adapter @@ -24,7 +25,6 @@ def download_wsdl_files(*wsdl_urls, generate=False): """Download WSDL files for NF-e, CT-e, MDF-e, and BP-e.""" - # Access the certificate and password from environment variables CERT_FILE = environ.get("CERT_FILE") CERT_PASSWORD = environ.get("CERT_PASSWORD") @@ -89,22 +89,24 @@ def download_wsdl_files(*wsdl_urls, generate=False): with open(wsdl_file, "w") as file: file.write(response.text) + if generate: + soap_dir = wsdl_dir.replace("wsdl", "soap").replace("/", ".") + command = [ + "xsdata", + "generate", + "--package", + soap_dir, + "--include-header", + wsdl_file, + ] + try: + subprocess.run(command, check=True) + logging.info("Successfully generated SOAP bindings for {wsdl_file}") + except subprocess.CalledProcessError as e: + logging.error( + f"Failed to generate SOAP bindings for {wsdl_file}: {e}" + ) + except Exception as e: _logger.error(f"Failed to download or save WSDL from {url}: {e}") raise - - if generate: - soap_dir = wsdl_dir.replace("wsdl", "soap") - command = [ - "xsdata", - "generate", - "--package", - soap_dir, - wsdl_dir, - ] - try: - subprocess.run(command, check=True) - logging.info("Successfully generated SOAP bindings.") - except subprocess.CalledProcessError as e: - logging.error(f"Failed to generate SOAP bindings: {e}") - sys.exit(1) From aad719ecec2c241911fa7bbab7d6c477ef2f5246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 05:38:24 +0000 Subject: [PATCH 16/38] proper NFe SOAP bindings --- nfelib/nfe/soap/v4_0/__init__.py | 95 ++-------------- nfelib/nfe/soap/v4_0/nfeautorizacao4.py | 122 ++++++++++++--------- nfelib/nfe/soap/v4_0/nfeconsulta4.py | 43 +++++--- nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py | 73 ++++++------ nfelib/nfe/soap/v4_0/nfeinutilizacao4.py | 31 ++++-- nfelib/nfe/soap/v4_0/nferetautorizacao4.py | 43 +++++--- nfelib/nfe/soap/v4_0/nfestatusservico4.py | 55 ++++++---- nfelib/nfe/soap/v4_0/recepcaoevento4.py | 55 ++++++---- 8 files changed, 254 insertions(+), 263 deletions(-) diff --git a/nfelib/nfe/soap/v4_0/__init__.py b/nfelib/nfe/soap/v4_0/__init__.py index 828da2bf..72aceaa5 100644 --- a/nfelib/nfe/soap/v4_0/__init__.py +++ b/nfelib/nfe/soap/v4_0/__init__.py @@ -1,97 +1,20 @@ -from nfelib.nfe.soap.v4_0.nfeautorizacao4 import ( - NfeAutorizacao4SoapNfeAutorizacaoLote, - NfeAutorizacao4SoapNfeAutorizacaoLoteZip, - NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput, - NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput, - NfeAutorizacao4SoapNfeAutorizacaoLoteInput, - NfeAutorizacao4SoapNfeAutorizacaoLoteOutput, - NfeDadosMsg as Nfeautorizacao4NfeDadosMsg, - NfeDadosMsgZip, - NfeMonitoria, - NfeResultMsg as Nfeautorizacao4NfeResultMsg, -) -from nfelib.nfe.soap.v4_0.nfeconsulta4 import ( - NfeConsultaProtocolo4SoapNfeConsultaNf, - NfeConsultaProtocolo4SoapNfeConsultaNfInput, - NfeConsultaProtocolo4SoapNfeConsultaNfOutput, - NfeDadosMsg as Nfeconsulta4NfeDadosMsg, - NfeResultMsg as Nfeconsulta4NfeResultMsg, -) -from nfelib.nfe.soap.v4_0.nfedistribuicaodfe import ( - NfeDistribuicaoDfeSoapNfeDistDfeInteresse, - NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput, - NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput, - NfeDistDfeInteresse, - NfeDistDfeInteresseResponse, -) -from nfelib.nfe.soap.v4_0.nfeinutilizacao4 import ( - NfeInutilizacao4SoapNfeInutilizacaoNf, - NfeInutilizacao4SoapNfeInutilizacaoNfInput, - NfeInutilizacao4SoapNfeInutilizacaoNfOutput, - NfeDadosMsg as Nfeinutilizacao4NfeDadosMsg, - NfeResultMsg as Nfeinutilizacao4NfeResultMsg, -) +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:49 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from nfelib.nfe.soap.v4_0.nferetautorizacao4 import ( + NfeDadosMsg, + NfeResultMsg, NfeRetAutorizacao4SoapNfeRetAutorizacaoLote, NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput, NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput, - NfeDadosMsg as Nferetautorizacao4NfeDadosMsg, - NfeResultMsg as Nferetautorizacao4NfeResultMsg, -) -from nfelib.nfe.soap.v4_0.nfestatusservico4 import ( - NfeStatusServico4SoapNfeStatusServicoNf, - NfeStatusServico4SoapNfeStatusServicoNfInput, - NfeStatusServico4SoapNfeStatusServicoNfOutput, - NfeDadosMsg as Nfestatusservico4NfeDadosMsg, - NfeResultMsg as Nfestatusservico4NfeResultMsg, -) -from nfelib.nfe.soap.v4_0.recepcaoevento4 import ( - NfeRecepcaoEvento4SoapNfeRecepcaoEvento, - NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput, - NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput, - NfeDadosMsg as Recepcaoevento4NfeDadosMsg, - NfeResultMsg as Recepcaoevento4NfeResultMsg, ) __all__ = [ - "NfeAutorizacao4SoapNfeAutorizacaoLote", - "NfeAutorizacao4SoapNfeAutorizacaoLoteZip", - "NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput", - "NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput", - "NfeAutorizacao4SoapNfeAutorizacaoLoteInput", - "NfeAutorizacao4SoapNfeAutorizacaoLoteOutput", - "Nfeautorizacao4NfeDadosMsg", - "NfeDadosMsgZip", - "NfeMonitoria", - "Nfeautorizacao4NfeResultMsg", - "NfeConsultaProtocolo4SoapNfeConsultaNf", - "NfeConsultaProtocolo4SoapNfeConsultaNfInput", - "NfeConsultaProtocolo4SoapNfeConsultaNfOutput", - "Nfeconsulta4NfeDadosMsg", - "Nfeconsulta4NfeResultMsg", - "NfeDistribuicaoDfeSoapNfeDistDfeInteresse", - "NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput", - "NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput", - "NfeDistDfeInteresse", - "NfeDistDfeInteresseResponse", - "NfeInutilizacao4SoapNfeInutilizacaoNf", - "NfeInutilizacao4SoapNfeInutilizacaoNfInput", - "NfeInutilizacao4SoapNfeInutilizacaoNfOutput", - "Nfeinutilizacao4NfeDadosMsg", - "Nfeinutilizacao4NfeResultMsg", "NfeRetAutorizacao4SoapNfeRetAutorizacaoLote", "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput", "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput", - "Nferetautorizacao4NfeDadosMsg", - "Nferetautorizacao4NfeResultMsg", - "NfeStatusServico4SoapNfeStatusServicoNf", - "NfeStatusServico4SoapNfeStatusServicoNfInput", - "NfeStatusServico4SoapNfeStatusServicoNfOutput", - "Nfestatusservico4NfeDadosMsg", - "Nfestatusservico4NfeResultMsg", - "NfeRecepcaoEvento4SoapNfeRecepcaoEvento", - "NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput", - "NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput", - "Recepcaoevento4NfeDadosMsg", - "Recepcaoevento4NfeResultMsg", + "NfeDadosMsg", + "NfeResultMsg", ] diff --git a/nfelib/nfe/soap/v4_0/nfeautorizacao4.py b/nfelib/nfe/soap/v4_0/nfeautorizacao4.py index f471d1f9..cf7d28a0 100644 --- a/nfelib/nfe/soap/v4_0/nfeautorizacao4.py +++ b/nfelib/nfe/soap/v4_0/nfeautorizacao4.py @@ -1,36 +1,16 @@ +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:43 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from dataclasses import dataclass, field -from nfelib import CommonMixin from typing import Dict, List, Optional - -@dataclass -class NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput(CommonMixin): - class Meta: - name = "Envelope" - namespace = "http://schemas.xmlsoap.org/soap/envelope/" - - Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) - ) - - @dataclass - class Body(CommonMixin): - nfeDadosMsgZip: Optional[str] = field( - default=None, - metadata={ - "type": "Element", - "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", - }, - ) +__NAMESPACE__ = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" @dataclass -class NfeDadosMsg(CommonMixin): +class NfeDadosMsg: class Meta: name = "nfeDadosMsg" namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" @@ -46,7 +26,7 @@ class Meta: @dataclass -class NfeDadosMsgZip(CommonMixin): +class NfeDadosMsgZip: class Meta: name = "nfeDadosMsgZip" namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" @@ -60,7 +40,7 @@ class Meta: @dataclass -class NfeMonitoria(CommonMixin): +class NfeMonitoria: class Meta: name = "nfeMonitoria" namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4" @@ -87,7 +67,7 @@ class Meta: @dataclass -class NfeResultMsg(CommonMixin): +class NfeResultMsg: class Meta: name = "nfeResultMsg" nillable = True @@ -104,30 +84,59 @@ class Meta: @dataclass -class NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput(CommonMixin): +class NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput.Body"] = ( - field( + body: Optional[ + "NfeAutorizacao4SoapNfeAutorizacaoLoteZipInput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, + ) + + @dataclass + class Body: + nfeDadosMsgZip: Optional[NfeDadosMsgZip] = field( default=None, metadata={ "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4", }, ) + + +@dataclass +class NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput: + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + body: Optional[ + "NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) - Header: Optional[ + header: Optional[ "NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput.Header" ] = field( default=None, metadata={ + "name": "Header", "type": "Element", }, ) @dataclass - class Body(CommonMixin): + class Body: nfeResultMsg: Optional[NfeResultMsg] = field( default=None, metadata={ @@ -136,17 +145,18 @@ class Body(CommonMixin): "nillable": True, }, ) - Fault: Optional[ + fault: Optional[ "NfeAutorizacao4SoapNfeAutorizacaoLoteZipOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ @@ -177,7 +187,7 @@ class Fault(CommonMixin): ) @dataclass - class Header(CommonMixin): + class Header: nfeMonitoria: Optional[NfeMonitoria] = field( default=None, metadata={ @@ -188,20 +198,21 @@ class Header(CommonMixin): @dataclass -class NfeAutorizacao4SoapNfeAutorizacaoLoteInput(CommonMixin): +class NfeAutorizacao4SoapNfeAutorizacaoLoteInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteInput.Body"] = field( + body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteInput.Body"] = field( default=None, metadata={ + "name": "Body", "type": "Element", }, ) @dataclass - class Body(CommonMixin): + class Body: nfeDadosMsg: Optional[NfeDadosMsg] = field( default=None, metadata={ @@ -212,28 +223,30 @@ class Body(CommonMixin): @dataclass -class NfeAutorizacao4SoapNfeAutorizacaoLoteOutput(CommonMixin): +class NfeAutorizacao4SoapNfeAutorizacaoLoteOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Body"] = field( + body: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Body"] = field( default=None, metadata={ + "name": "Body", "type": "Element", }, ) - Header: Optional["NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Header"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + header: Optional[ + "NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Header" + ] = field( + default=None, + metadata={ + "name": "Header", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeResultMsg: Optional[NfeResultMsg] = field( default=None, metadata={ @@ -242,17 +255,18 @@ class Body(CommonMixin): "nillable": True, }, ) - Fault: Optional[ + fault: Optional[ "NfeAutorizacao4SoapNfeAutorizacaoLoteOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ @@ -283,7 +297,7 @@ class Fault(CommonMixin): ) @dataclass - class Header(CommonMixin): + class Header: nfeMonitoria: Optional[NfeMonitoria] = field( default=None, metadata={ diff --git a/nfelib/nfe/soap/v4_0/nfeconsulta4.py b/nfelib/nfe/soap/v4_0/nfeconsulta4.py index b4a90492..c1c1a111 100644 --- a/nfelib/nfe/soap/v4_0/nfeconsulta4.py +++ b/nfelib/nfe/soap/v4_0/nfeconsulta4.py @@ -1,10 +1,16 @@ +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:46 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from dataclasses import dataclass, field -from nfelib import CommonMixin from typing import List, Optional +__NAMESPACE__ = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4" + @dataclass -class NfeDadosMsg(CommonMixin): +class NfeDadosMsg: class Meta: name = "nfeDadosMsg" namespace = ( @@ -22,7 +28,7 @@ class Meta: @dataclass -class NfeResultMsg(CommonMixin): +class NfeResultMsg: class Meta: name = "nfeResultMsg" nillable = True @@ -41,20 +47,21 @@ class Meta: @dataclass -class NfeConsultaProtocolo4SoapNfeConsultaNfInput(CommonMixin): +class NfeConsultaProtocolo4SoapNfeConsultaNfInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeConsultaProtocolo4SoapNfeConsultaNfInput.Body"] = field( + body: Optional["NfeConsultaProtocolo4SoapNfeConsultaNfInput.Body"] = field( default=None, metadata={ + "name": "Body", "type": "Element", }, ) @dataclass - class Body(CommonMixin): + class Body: nfeDadosMsg: Optional[NfeDadosMsg] = field( default=None, metadata={ @@ -65,22 +72,23 @@ class Body(CommonMixin): @dataclass -class NfeConsultaProtocolo4SoapNfeConsultaNfOutput(CommonMixin): +class NfeConsultaProtocolo4SoapNfeConsultaNfOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeConsultaProtocolo4SoapNfeConsultaNfOutput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + body: Optional[ + "NfeConsultaProtocolo4SoapNfeConsultaNfOutput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeResultMsg: Optional[NfeResultMsg] = field( default=None, metadata={ @@ -89,17 +97,18 @@ class Body(CommonMixin): "nillable": True, }, ) - Fault: Optional[ + fault: Optional[ "NfeConsultaProtocolo4SoapNfeConsultaNfOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ diff --git a/nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py b/nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py index ed75b1be..28e364ba 100644 --- a/nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py +++ b/nfelib/nfe/soap/v4_0/nfedistribuicaodfe.py @@ -1,10 +1,16 @@ +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:45 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from dataclasses import dataclass, field -from nfelib import CommonMixin from typing import List, Optional +__NAMESPACE__ = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe" + @dataclass -class NfeDistDfeInteresse(CommonMixin): +class NfeDistDfeInteresse: class Meta: name = "nfeDistDFeInteresse" namespace = ( @@ -19,7 +25,7 @@ class Meta: ) @dataclass - class NfeDadosMsg(CommonMixin): + class NfeDadosMsg: content: List[object] = field( default_factory=list, metadata={ @@ -31,7 +37,7 @@ class NfeDadosMsg(CommonMixin): @dataclass -class NfeDistDfeInteresseResponse(CommonMixin): +class NfeDistDfeInteresseResponse: class Meta: name = "nfeDistDFeInteresseResponse" namespace = ( @@ -48,7 +54,7 @@ class Meta: ) @dataclass - class NfeDistDfeInteresseResult(CommonMixin): + class NfeDistDfeInteresseResult: content: List[object] = field( default_factory=list, metadata={ @@ -60,22 +66,23 @@ class NfeDistDfeInteresseResult(CommonMixin): @dataclass -class NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput(CommonMixin): +class NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + body: Optional[ + "NfeDistribuicaoDfeSoapNfeDistDfeInteresseInput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeDistDFeInteresse: Optional[NfeDistDfeInteresse] = field( default=None, metadata={ @@ -86,42 +93,44 @@ class Body(CommonMixin): @dataclass -class NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput(CommonMixin): +class NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput.Body"] = ( - field( + body: Optional[ + "NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, + ) + + @dataclass + class Body: + nfeDistDFeInteresseResponse: Optional[ + NfeDistDfeInteresseResponse + ] = field( default=None, metadata={ "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe", }, ) - ) - - @dataclass - class Body(CommonMixin): - nfeDistDFeInteresseResponse: Optional[NfeDistDfeInteresseResponse] = ( - field( - default=None, - metadata={ - "type": "Element", - "namespace": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe", - }, - ) - ) - Fault: Optional[ + fault: Optional[ "NfeDistribuicaoDfeSoapNfeDistDfeInteresseOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ diff --git a/nfelib/nfe/soap/v4_0/nfeinutilizacao4.py b/nfelib/nfe/soap/v4_0/nfeinutilizacao4.py index d996ee95..1582cc18 100644 --- a/nfelib/nfe/soap/v4_0/nfeinutilizacao4.py +++ b/nfelib/nfe/soap/v4_0/nfeinutilizacao4.py @@ -1,10 +1,16 @@ +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:44 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from dataclasses import dataclass, field -from nfelib import CommonMixin from typing import List, Optional +__NAMESPACE__ = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeInutilizacao4" + @dataclass -class NfeDadosMsg(CommonMixin): +class NfeDadosMsg: class Meta: name = "nfeDadosMsg" namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeInutilizacao4" @@ -20,7 +26,7 @@ class Meta: @dataclass -class NfeResultMsg(CommonMixin): +class NfeResultMsg: class Meta: name = "nfeResultMsg" nillable = True @@ -37,20 +43,21 @@ class Meta: @dataclass -class NfeInutilizacao4SoapNfeInutilizacaoNfInput(CommonMixin): +class NfeInutilizacao4SoapNfeInutilizacaoNfInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeInutilizacao4SoapNfeInutilizacaoNfInput.Body"] = field( + body: Optional["NfeInutilizacao4SoapNfeInutilizacaoNfInput.Body"] = field( default=None, metadata={ + "name": "Body", "type": "Element", }, ) @dataclass - class Body(CommonMixin): + class Body: nfeDadosMsg: Optional[NfeDadosMsg] = field( default=None, metadata={ @@ -61,20 +68,21 @@ class Body(CommonMixin): @dataclass -class NfeInutilizacao4SoapNfeInutilizacaoNfOutput(CommonMixin): +class NfeInutilizacao4SoapNfeInutilizacaoNfOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeInutilizacao4SoapNfeInutilizacaoNfOutput.Body"] = field( + body: Optional["NfeInutilizacao4SoapNfeInutilizacaoNfOutput.Body"] = field( default=None, metadata={ + "name": "Body", "type": "Element", }, ) @dataclass - class Body(CommonMixin): + class Body: nfeResultMsg: Optional[NfeResultMsg] = field( default=None, metadata={ @@ -83,17 +91,18 @@ class Body(CommonMixin): "nillable": True, }, ) - Fault: Optional[ + fault: Optional[ "NfeInutilizacao4SoapNfeInutilizacaoNfOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ diff --git a/nfelib/nfe/soap/v4_0/nferetautorizacao4.py b/nfelib/nfe/soap/v4_0/nferetautorizacao4.py index 60231752..26f0c4a2 100644 --- a/nfelib/nfe/soap/v4_0/nferetautorizacao4.py +++ b/nfelib/nfe/soap/v4_0/nferetautorizacao4.py @@ -1,10 +1,16 @@ +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:49 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from dataclasses import dataclass, field -from nfelib import CommonMixin from typing import List, Optional +__NAMESPACE__ = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRetAutorizacao4" + @dataclass -class NfeDadosMsg(CommonMixin): +class NfeDadosMsg: class Meta: name = "nfeDadosMsg" namespace = ( @@ -22,7 +28,7 @@ class Meta: @dataclass -class NfeResultMsg(CommonMixin): +class NfeResultMsg: class Meta: name = "nfeResultMsg" nillable = True @@ -41,22 +47,23 @@ class Meta: @dataclass -class NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput(CommonMixin): +class NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + body: Optional[ + "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteInput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeDadosMsg: Optional[NfeDadosMsg] = field( default=None, metadata={ @@ -67,22 +74,23 @@ class Body(CommonMixin): @dataclass -class NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput(CommonMixin): +class NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional[ + body: Optional[ "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput.Body" ] = field( default=None, metadata={ + "name": "Body", "type": "Element", }, ) @dataclass - class Body(CommonMixin): + class Body: nfeResultMsg: Optional[NfeResultMsg] = field( default=None, metadata={ @@ -91,17 +99,18 @@ class Body(CommonMixin): "nillable": True, }, ) - Fault: Optional[ + fault: Optional[ "NfeRetAutorizacao4SoapNfeRetAutorizacaoLoteOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ diff --git a/nfelib/nfe/soap/v4_0/nfestatusservico4.py b/nfelib/nfe/soap/v4_0/nfestatusservico4.py index 7748696c..7849b1a3 100644 --- a/nfelib/nfe/soap/v4_0/nfestatusservico4.py +++ b/nfelib/nfe/soap/v4_0/nfestatusservico4.py @@ -1,10 +1,16 @@ +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:47 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from dataclasses import dataclass, field -from nfelib import CommonMixin from typing import List, Optional +__NAMESPACE__ = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4" + @dataclass -class NfeDadosMsg(CommonMixin): +class NfeDadosMsg: class Meta: name = "nfeDadosMsg" namespace = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4" @@ -20,7 +26,7 @@ class Meta: @dataclass -class NfeResultMsg(CommonMixin): +class NfeResultMsg: class Meta: name = "nfeResultMsg" nillable = True @@ -37,22 +43,23 @@ class Meta: @dataclass -class NfeStatusServico4SoapNfeStatusServicoNfInput(CommonMixin): +class NfeStatusServico4SoapNfeStatusServicoNfInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeStatusServico4SoapNfeStatusServicoNfInput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + body: Optional[ + "NfeStatusServico4SoapNfeStatusServicoNfInput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeDadosMsg: Optional[NfeDadosMsg] = field( default=None, metadata={ @@ -63,22 +70,23 @@ class Body(CommonMixin): @dataclass -class NfeStatusServico4SoapNfeStatusServicoNfOutput(CommonMixin): +class NfeStatusServico4SoapNfeStatusServicoNfOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeStatusServico4SoapNfeStatusServicoNfOutput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + body: Optional[ + "NfeStatusServico4SoapNfeStatusServicoNfOutput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeResultMsg: Optional[NfeResultMsg] = field( default=None, metadata={ @@ -87,17 +95,18 @@ class Body(CommonMixin): "nillable": True, }, ) - Fault: Optional[ + fault: Optional[ "NfeStatusServico4SoapNfeStatusServicoNfOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ diff --git a/nfelib/nfe/soap/v4_0/recepcaoevento4.py b/nfelib/nfe/soap/v4_0/recepcaoevento4.py index d4bf1674..69b3e520 100644 --- a/nfelib/nfe/soap/v4_0/recepcaoevento4.py +++ b/nfelib/nfe/soap/v4_0/recepcaoevento4.py @@ -1,10 +1,16 @@ +"""This file was generated by xsdata, v24.3.1, on 2024-04-05 01:48:48 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" from dataclasses import dataclass, field -from nfelib import CommonMixin from typing import List, Optional +__NAMESPACE__ = "http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4" + @dataclass -class NfeDadosMsg(CommonMixin): +class NfeDadosMsg: class Meta: name = "nfeDadosMsg" namespace = ( @@ -22,7 +28,7 @@ class Meta: @dataclass -class NfeResultMsg(CommonMixin): +class NfeResultMsg: class Meta: name = "nfeResultMsg" nillable = True @@ -41,22 +47,23 @@ class Meta: @dataclass -class NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput(CommonMixin): +class NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + body: Optional[ + "NfeRecepcaoEvento4SoapNfeRecepcaoEventoInput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeDadosMsg: Optional[NfeDadosMsg] = field( default=None, metadata={ @@ -67,22 +74,23 @@ class Body(CommonMixin): @dataclass -class NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput(CommonMixin): +class NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput: class Meta: name = "Envelope" namespace = "http://schemas.xmlsoap.org/soap/envelope/" - Body: Optional["NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput.Body"] = ( - field( - default=None, - metadata={ - "type": "Element", - }, - ) + body: Optional[ + "NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput.Body" + ] = field( + default=None, + metadata={ + "name": "Body", + "type": "Element", + }, ) @dataclass - class Body(CommonMixin): + class Body: nfeResultMsg: Optional[NfeResultMsg] = field( default=None, metadata={ @@ -91,17 +99,18 @@ class Body(CommonMixin): "nillable": True, }, ) - Fault: Optional[ + fault: Optional[ "NfeRecepcaoEvento4SoapNfeRecepcaoEventoOutput.Body.Fault" ] = field( default=None, metadata={ + "name": "Fault", "type": "Element", }, ) @dataclass - class Fault(CommonMixin): + class Fault: faultcode: Optional[str] = field( default=None, metadata={ From 376e3546ed7ab6d1ec7adda6594aaf001735ccc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 05:39:07 +0000 Subject: [PATCH 17/38] NFe SOAP client clean up --- nfelib/nfe/client/v4_0/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/nfelib/nfe/client/v4_0/__init__.py b/nfelib/nfe/client/v4_0/__init__.py index aa5f902d..fae1b719 100644 --- a/nfelib/nfe/client/v4_0/__init__.py +++ b/nfelib/nfe/client/v4_0/__init__.py @@ -51,14 +51,6 @@ from nfelib.nfe.soap.v4_0.recepcaoevento4 import ( NfeRecepcaoEvento4SoapNfeRecepcaoEvento, ) -from nfelib.nfe_evento_cancel.bindings.v1_0.evento_canc_nfe_v1_00 import ( - Tevento as TeventoCancel, -) -from nfelib.nfe_evento_cancel.bindings.v1_0.leiaute_evento_canc_nfe_v1_00 import ( - TenvEvento, - TretEnvEvento, -) -from nfelib.nfe_evento_cce.bindings.v1_0.leiaute_cce_v1_00 import Tevento as TeventoCCe # TODO import file generated by test_generate_servers.py instead SERVERS_NFE = { From d7fc2c2fdede13cf53a8a21fa92e345782a2bf54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 05:39:48 +0000 Subject: [PATCH 18/38] add NFe SOAP client test --- tests/nfe/test_client.py | 208 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 tests/nfe/test_client.py diff --git a/tests/nfe/test_client.py b/tests/nfe/test_client.py new file mode 100644 index 00000000..522de635 --- /dev/null +++ b/tests/nfe/test_client.py @@ -0,0 +1,208 @@ +import logging +import io +import os +import time +from os import environ +from pathlib import Path +from unittest import TestCase, mock + +from decorator import decorate +from erpbrasil.assinatura import certificado as cert +from erpbrasil.assinatura import misc +from nfelib.nfe.bindings.v4_0.nfe_v4_00 import Nfe +from nfelib.nfe.bindings.v4_0.proc_nfe_v4_00 import TnfeProc +from requests import Session +from requests_pkcs12 import Pkcs12Adapter +from xsdata.formats.dataclass.parsers import XmlParser +from xsdata.formats.dataclass.serializers import XmlSerializer +from xsdata.formats.dataclass.transports import DefaultTransport + +from nfelib.nfe.client.v4_0 import NfeClient + +response_status = b""" +2SVRS202305251555107Servico em Operacao422023-06-11T00:15:00-03:001""" + +response_envia_documento = b""" +2SVRS202305251555103Lote recebido com sucesso422023-06-11T01:18:19-03:004230022021132321""" + +response_consulta_documento = b""" +2SVRS202305251555217Rejeicao: NF-e nao consta na base de dados da SEFAZ422023-06-11T01:20:55-03:0042230675277525000259550010000364481754015406""" + +response_consulta_recibo = b""" +2SVRS202305261028423002202113232104Lote processado422023-06-11T01:18:19-03:002SVRS202305261028422306752775250002595500100003644817540154062023-06-11T01:18:19-03:00IoYUWXt2fIiRXb7UYRgl77c6Zlk=297Rejeicao: Assinatura difere do calculado""" + +response_cancela_documento = b"""2SVRS202305251555215Rejeicao: Falha no schema XML""" # TODO not a valid cancelamento + + +_logger = logging.getLogger(__name__) + + +def _only_if_valid_certificate(method, self): + if self.valid_certificate: + return method(self) + return lambda: _logger.info( + "Skipping test because you didn't provide a valid A1 certificate" + ) + + +def only_if_valid_certificate(method): + return decorate(method, _only_if_valid_certificate) + + +SERVER = "https://nfe-homologacao.svrs.rs.gov.br" # TODO should be a parameter + + +class SoapTest(TestCase): + """ + Tests are mocked because the fisc server requires a valid A1 certificate. + But for each test, you can also activate the non mocked version + if you export the CERT_FILE and CERT_PASSWORD ENV variables before running the test: + export CERT_PASSWORD=... + export CERT_FILE=... + export NFE_FILE=... + + To fix/adjust the _mocked tests, a way is to use a real A1 certificate and make the + associated non mocked test fail (like by using assertEqual with a bad value) + while priting the response in the NfeClient send method. Then use it as the mocked + response. + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + # TODO customize NFe path via ENV var? + parser = XmlParser() + if environ.get("NFE_FILE"): + nfe_path = environ["NFE_FILE"] + else: + nfe_path = "tests/nfe/procNFe.xml" + + nfe_proc = parser.from_path( + Path(nfe_path), + TnfeProc, + ) + + if environ.get("CERT_FILE") and environ.get("CERT_PASSWORD"): + cls.valid_certificate = True + with open(environ["CERT_FILE"], "rb") as pkcs12_file: + cls.cert_data = pkcs12_file.read() + cls.cert_password = environ["CERT_PASSWORD"] + cls.fake_certificate = False + else: + cls.valid_certificate = False + valid = (True,) + cls.cert_password = "123456" + issuer = "EMISSOR A TESTE" + country = "BR" + subject = "CERTIFICADO VALIDO TESTE" + # cert_type = "nfe-e" + cls.cert_data = misc.create_fake_certificate_file( + valid, cls.cert_password, issuer, country, subject + ) + cls.fake_certificate = True + + cls.nfe = Nfe(infNFe=nfe_proc.NFe.infNFe) # , signature=nfe_proc.NFe.signature) + serializer = XmlSerializer() + cls.nfe_xml = serializer.render( + obj=cls.nfe, ns_map={None: "http://www.portalfiscal.inf.br/nfe"} + ) # .replace('\n', '').replace('\r', '') + cls.client = NfeClient( + ambiente="2", + uf="41", # cls.nfe.infNFe.ide.cUF.value, + pkcs12_data=cls.cert_data, + pkcs12_password=cls.cert_password, + fake_certificate=cls.fake_certificate, + ) + cls.signed_nfe_xml = cls.nfe.to_xml(pkcs12_data=cls.cert_data, pkcs12_password=cls.cert_password, doc_id=cls.nfe.infNFe.Id) + + @only_if_valid_certificate + def test_0_status(self): + res = self.client.status_servico() + self.assertEqual(res.cStat, "107") + + @mock.patch.object(DefaultTransport, "post") + def test_0_status_mocked(self, mock_post): + mock_post.return_value = response_status + res = self.client.status_servico() + self.assertEqual(res.cStat, "107") + + @only_if_valid_certificate + def test_1_envia_documento(self): + res = self.client.envia_documento([self.signed_nfe_xml]) + self.assertEqual(res.cStat, "103") + print(res) + import time + + time.sleep(int(res.infRec.tMed)) + res = self.client.consulta_recibo(numero=res.infRec.nRec) + print(res) + # res = self.client.consulta_documento(self.nfe.infNFe.Id[3:]) + # # we actually test the NFe is not found because it is the test environment + # print(res) + # breakpoint() + # self.assertIn(res.cStat, ("217", "526")) + + @mock.patch.object(DefaultTransport, "post") + def test_1_envia_documento_mocked(self, mock_post): + mock_post.return_value = response_envia_documento + res = self.client.envia_documento([self.signed_nfe_xml]) + self.assertEqual(res.cStat, "103") + + @only_if_valid_certificate + def test_2_consulta_documento(self): + res = self.client.consulta_documento(self.nfe.infNFe.Id[3:]) + # we actually test the NFe is not found because it is the test environment + self.assertIn(res.cStat, ("217", "526")) + + @mock.patch.object(DefaultTransport, "post") + def test_2_consulta_documento_mocked(self, mock_post): + mock_post.return_value = response_consulta_documento + res = self.client.consulta_documento(self.nfe.infNFe.Id[3:]) + # we actually test the NFe is not found because it is the test environment + self.assertEqual(res.cStat, "217") + + def test_3_envia_inutilizacao(self): + pass + + @only_if_valid_certificate + def test_4_consulta_recibo(self): + nrec = "423002202113232" + res = self.client.consulta_recibo(nrec) + # we actually test the NFe is not found because it is the test environment + self.assertEqual(res.cStat, "106") # TODO should be 104 normally + + @mock.patch.object(DefaultTransport, "post") + def test_4_consulta_recibo_mocked(self, mock_post): + mock_post.return_value = response_consulta_recibo + nrec = "423002202113232" + res = self.client.consulta_recibo(nrec) + # we actually test the NFe is not found because it is the test environment + self.assertEqual(res.cStat, "104") + + def test_5_enviar_lote_evento(self): # (cancela documento) + pass + + @only_if_valid_certificate + def test_envia_e_consulta_e_cancela(self): + "isso mais ou menos test o processa_documento do erpbrasil.edoc" + proc_envio = self.client.envia_documento(self.signed_nfe_xml) + self.client._aguarda_tempo_medio(proc_envio) + res = self.client.consulta_recibo(proc_envio=proc_envio) + # TODO por algum motivo eu pego um erro como: + # 2SVRS202305261028422306752775250002595500100003644817540154062023-06-12T22:57:51-03:00IoYUWXt2fIiRXb7UYRgl77c6Zlk=297Rejeicao: Assinatura difere do calculado + self.assertEqual(res.cStat, "104") + + cancel_event = self.client.cancela_documento( + self.nfe.infNFe.Id[3:], "423002202113232", "Era apenas um teste." + ) + self.client.enviar_lote_evento([cancel_event]) + + # self.assertEqual(1, 2) + + @only_if_valid_certificate + def test_inutilizacao(self): + inut_event = self.client.inutilizacao( + "81583054000129", "55", "1", "2", "2", "Era apenas um teste" + ) + self.client.envia_inutilizacao(inut_event) + From 70d0b7ca2dad606d30948bbc83afbe2c608b3676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 05:45:00 +0000 Subject: [PATCH 19/38] clean up CTe client files layout --- nfelib/cte/client/v4_0/__init__.py | 0 nfelib/cte/client/{ => v4_0}/servers.py | 0 nfelib/cte/client/{ => v4_0}/servers_scraper.py | 8 +------- 3 files changed, 1 insertion(+), 7 deletions(-) create mode 100644 nfelib/cte/client/v4_0/__init__.py rename nfelib/cte/client/{ => v4_0}/servers.py (100%) rename nfelib/cte/client/{ => v4_0}/servers_scraper.py (68%) diff --git a/nfelib/cte/client/v4_0/__init__.py b/nfelib/cte/client/v4_0/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nfelib/cte/client/servers.py b/nfelib/cte/client/v4_0/servers.py similarity index 100% rename from nfelib/cte/client/servers.py rename to nfelib/cte/client/v4_0/servers.py diff --git a/nfelib/cte/client/servers_scraper.py b/nfelib/cte/client/v4_0/servers_scraper.py similarity index 68% rename from nfelib/cte/client/servers_scraper.py rename to nfelib/cte/client/v4_0/servers_scraper.py index 926a6ffb..ba334b2b 100644 --- a/nfelib/cte/client/servers_scraper.py +++ b/nfelib/cte/client/v4_0/servers_scraper.py @@ -1,12 +1,6 @@ -import logging + from pathlib import Path -from typing import Dict, Any -from io import StringIO # Add this import -import pandas as pd -import requests -from bs4 import BeautifulSoup -from xsdata.formats.dataclass.serializers import PycodeSerializer from nfelib.utils.servers_scraper import fetch_servers, save_servers # Constants From b4e848168238d18ad77cfda0fc352a83b9f1c409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 16:08:32 +0000 Subject: [PATCH 20/38] add brazil-fiscal-client test dep --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index f9b1c63f..d7252852 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ test = [ "brazilfiscalreport", "beautifulsoup4", "pandas", + "brazil-fiscal-client", ] [tool.setuptools] From 17d58b10ce81f9a8acbfabbdd2877d67da19a78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 16:15:08 +0000 Subject: [PATCH 21/38] add decorator test dep --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d7252852..1a70b406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ test = [ "pytest-cov", "xmldiff", "requests", + "decorator", "beautifulsoup4", "erpbrasil.assinatura", "brazilfiscalreport", From 6cc6879af34995f96e23c28078af3df2b868889b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sun, 23 Mar 2025 22:19:22 +0000 Subject: [PATCH 22/38] added missing NFe client test fixture --- tests/nfe/fixtures/procNFe.xml | 183 +++++++++++++++++++++++++++++++++ tests/nfe/test_client.py | 2 +- 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 tests/nfe/fixtures/procNFe.xml diff --git a/tests/nfe/fixtures/procNFe.xml b/tests/nfe/fixtures/procNFe.xml new file mode 100644 index 00000000..26c416e9 --- /dev/null +++ b/tests/nfe/fixtures/procNFe.xml @@ -0,0 +1,183 @@ + + + + + + 41 + 61275652 + VENDA PRODUC.DO ESTABELEC + 55 + 1 + 46320 + 2017-07-12T09:45:50-03:00 + 2017-07-12T09:45:50-03:00 + 1 + 2 + 4118402 + 1 + 1 + 7 + 2 + 1 + 0 + 1 + 0 + UNICO V8.0 + + + 06117473000150 + UNIMAKE SOLUCOES CORPORATIVAS LTDA + UNIMAKE - PARANAVAI + + RUA ANTONIO FELIPE + 01500 + CENTRO + 4118402 + PARANAVAI + PR + 87704030 + 1058 + BRASIL + 04431414900 + + 9032000301 + 14018 + 6202300 + 1 + + + 27373722000148 + NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL + + RUA DUARTINA + 850 + JARDIM SOTO + 3511102 + CATANDUVA + SP + 15810150 + 1058 + BRASIL + 01735237730 + + 1 + 260205835115 + + + + 01042 + + NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL + 84714900 + 6101 + LU + 1 + 84.9000000000 + 84.90 + + LU + 1 + 84.9000000000 + 1 + 230772 + 1 + + + 16.29 + + + 0 + 101 + 2.4199 + 2.05 + + + + + 99 + 0.00 + 0.0000 + 0.00 + + + + + 99 + 0.00 + 0.0000 + 0.00 + + + + + + + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 84.90 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 0.00 + 84.90 + 16.29 + + + + 0 + + + + 01 + 84.90 + + + + ;CONTROLE: 0000178652;PEDIDO(S) ATENDIDO(S): 230772;Empresa optante pelo simples nacional, conforme lei compl. 128 de 19/12/2008;Permite o aproveitamento do credito de ICMS no valor de R$ 2,05, correspondente ao percentual de 2,42% . Nos termos do Art. 23 - LC 123/2006 (Resolucoes CGSN n. 10/2007 e 53/2008);Voce pagou aproximadamente: R$ 10,35 trib. federais / R$ 5,94 trib. estaduais / R$ 0,00 trib. municipais. Fonte: IBPT 16.2.A Ar5Fr7; + + + + + + + + + + + + + +GzukUqyGg3ksWFUU/e6HsguR0A= + + + Wm3gE99vp71Qf9OQFu0JOpMHQ6C35G5kiXssVroKz9YYaoM7Yp17bnX+5NH+2mTL11Wh53bPdwgCBZBJ1pmDm8FLeCTHoQzSVkW4hvLZKCPNmKNiuVQGXbME3TlvzOjrTawYTqB/V6gMohwXB+kYY5D+aE2TnLfENvaxoySGMcYORIc0jPnT7AydSGRiwPkhvlkftiGWz5CmabHk6oBX0+U7FyHkvoheNrOm9h9LmZYzG/nbJG0LQxA7mXhguZKGFJjmJ69fyqPZN+34PTmq84YKLsMc0Q5gOsxWSQGjnbZHsLCqnugScnx3fEbhBLhh9mETFAMY/6tshoVRKq3RKA== + + + MIIH9TCCBd2gAwIBAgIIYuKbQzRLrNswDQYJKoZIhvcNAQELBQAwczELMAkGA1UEBhMCQlIxEzARBgNVBAoTCklDUC1CcmFzaWwxNjA0BgNVBAsTLVNlY3JldGFyaWEgZGEgUmVjZWl0YSBGZWRlcmFsIGRvIEJyYXNpbCAtIFJGQjEXMBUGA1UEAxMOQUMgU0FGRVdFQiBSRkIwHhcNMTcwNDI4MTMwMTM0WhcNMTgwNDI4MTMwMTM0WjCB5zELMAkGA1UEBhMCQlIxEzARBgNVBAoTCklDUC1CcmFzaWwxCzAJBgNVBAgTAlBSMRIwEAYDVQQHEwlQQVJBTkFWQUkxNjA0BgNVBAsTLVNlY3JldGFyaWEgZGEgUmVjZWl0YSBGZWRlcmFsIGRvIEJyYXNpbCAtIFJGQjEWMBQGA1UECxMNUkZCIGUtQ05QSiBBMTESMBAGA1UECxMJQVIgRlVUVVJBMT4wPAYDVQQDEzVVTklNQUtFIFNPTFVDT0VTIENPUlBPUkFUSVZBUyBMVERBIEVQUDowNjExNzQ3MzAwMDE1MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqCax/tAWWvsUHAgAEfJu/KoNJu16dDu40gvvDUcVFz6mGkyo76idp+0sh/NM2s7++5OVfOOke92RX5DXRNv/HPqr8syyDo5TQOlaofktyb6I2Ijm8JKBS95wGjohCiAH+0+wRnOzMJCNbVO0D8F5LyAjLVzokcb26xWbHg8e4rYI7dRsf0mlBrwloUR8Cc5XEGlN3evHaRfOZOlhuAUMrznmkDJ8BUTUZWp2r+h4oeQmXgly2RvP8u+SU2QrmgqnAD2m+aQbt7iHhLrJxsY05jUfATOVjQbad0DwNBLTBXMrLVq/lblqZCbvr9mtOmoIvUVro0ozkZESVcJq6LZccCAwEAAaOCAxYwggMSMB8GA1UdIwQYMBaAFN9FT0/H4dw4zEoMIOf46VmtH15hMA4GA1UdDwEB/wQEAwIF4DBtBgNVHSAEZjBkMGIGBmBMAQIBMzBYMFYGCCsGAQUFBwIBFkpodHRwOi8vcmVwb3NpdG9yaW8uYWNzYWZld2ViLmNvbS5ici9hYy1zYWZld2VicmZiL2FjLXNhZmV3ZWItcmZiLXBjLWExLnBkZjCB/wYDVR0fBIH3MIH0ME+gTaBLhklodHRwOi8vcmVwb3NpdG9yaW8uYWNzYWZld2ViLmNvbS5ici9hYy1zYWZld2VicmZiL2xjci1hYy1zYWZld2VicmZidjIuY3JsMFCgTqBMhkpodHRwOi8vcmVwb3NpdG9yaW8yLmFjc2FmZXdlYi5jb20uYnIvYWMtc2FmZXdlYnJmYi9sY3ItYWMtc2FmZXdlYnJmYnYyLmNybDBPoE2gS4ZJaHR0cDovL2FjcmVwb3NpdG9yaW8uaWNwYnJhc2lsLmdvdi5ici9sY3IvU0FGRVdFQi9sY3ItYWMtc2FmZXdlYnJmYnYyLmNybDCBiwYIKwYBBQUHAQEEfzB9MFEGCCsGAQUFBzAChkVodHRwOi8vcmVwb3NpdG9yaW8uYWNzYWZld2ViLmNvbS5ici9hYy1zYWZld2VicmZiL2FjLXNhZmV3ZWJyZmJ2Mi5wN2IwKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3NwLmFjc2FmZXdlYi5jb20uYnIwgbUGA1UdEQSBrTCBqoEVU0VSR0lPQFVOSU1BS0UuQ09NLkJSoCMGBWBMAQMCoBoTGFNFUkdJTyBDQVNURUxBTyBQSU5IRUlST6AZBgVgTAEDA6AQEw4wNjExNzQ3MzAwMDE1MKA4BgVgTAEDBKAvEy0xODAyMTk3MDc4MDc2MzA3OTUzMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDCgFwYFYEwBAwegDhMMMDAwMDAwMDAwMDAwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBI/cnWoXnCRk7YoXoADPJazXQryZQQQK9eq/IT4wejAup05j6e4Ww4kH5XWOdmD0wSrC7s5JnM4jVdFVzwz4JH7NCRenDE5nItaGbwhKNffV6yoa7YOgXR6IosQ8Vg1VAvfFHUscf6sbXWgochm9ByNp82UcPwCwitpi5eGwUKRiKstKCAMYRkqtNzng3kkLNSGBhxtJhahsPJ+LqrN7iXLni70tLg4b4MUgf4jQd1C8R0fe8mg0lq35zeiWlIRICOtSz1ph0eaH4sycJ9KB1BXcdAGZNv75YzwUxYuUvTjaH26SFbkYKMNdfOBQQa6u3PYafI1cD9d9mhKEdAQAhOZ1gRe1ugFmQbG4MqOZOp4aaSMCODM3KSPkkOwCncMRMi8j9z67PUl6xbXpsghhFdYzLG4hsHWEOFWpNett9Hph+iplcdhwfkkGw0xEYTVZgp65CJig/2iyW0Gal3fpZ6/wRazhj90Emv6Q82Iwep6/soUsZ43ApNMlykXEV8e+Sr8tpyxoDtKqcF3gpUErlzQf/AHz55+Fdm7215qEcLUWBZGhs3k8YexB7Vjm742NZQa9yzdbcoA13QLPMfD2nxv0Xba01VdzLu85Q1/VBO0qrHPb1CAWw4TE2elpTx2uyc4A+oxEYafrLgT+lLC/0u4llEJx5X6ElWAcBW4DG8hw== + + + + + + + 2 + PR-v4_0_1 + 41170706117473000150550010000463202612756525 + 2017-07-12T10:03:59-03:00 + 141170000487910 + +GzukUqyGg3ksWFUU/e6HsguR0A= + 100 + Autorizado o uso da NF-e + + + diff --git a/tests/nfe/test_client.py b/tests/nfe/test_client.py index 522de635..e9ea2332 100644 --- a/tests/nfe/test_client.py +++ b/tests/nfe/test_client.py @@ -75,7 +75,7 @@ def setUpClass(cls): if environ.get("NFE_FILE"): nfe_path = environ["NFE_FILE"] else: - nfe_path = "tests/nfe/procNFe.xml" + nfe_path = "tests/nfe/fixtures/procNFe.xml" nfe_proc = parser.from_path( Path(nfe_path), From 03afde82df77ddffbdc90f25ed678b355d9e684f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:00:10 +0000 Subject: [PATCH 23/38] mypy checker uses types-requests --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33ed13fd..40d34d93 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,8 @@ repos: hooks: - id: mypy files: ^(nfelib/) - args: [ "--check-untyped-defs", "--ignore-missing-imports" ] + additional_dependencies: + - types-requests - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 hooks: From ee5ce65daa91ad9230a10dfe935a23c783f9ffe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:00:22 +0000 Subject: [PATCH 24/38] pre-commit fixes --- nfelib/utils/servers_scraper.py | 24 +++++++++++------------- nfelib/utils/wsdl_downloader.py | 4 ++-- tests/nfe/test_client.py | 17 +++++++---------- tests/nfe/test_servers.py | 7 ++++--- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/nfelib/utils/servers_scraper.py b/nfelib/utils/servers_scraper.py index 798d9003..f555895d 100644 --- a/nfelib/utils/servers_scraper.py +++ b/nfelib/utils/servers_scraper.py @@ -1,9 +1,9 @@ # Copyright (C) 2024 Raphaël Valyi - Akretion import logging -from pathlib import Path -from typing import Dict, Any, Tuple from io import StringIO +from pathlib import Path +from typing import Any import pandas as pd import requests @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) -def fetch_servers(prod_url: str, dev_url: str) -> Tuple[Dict[str, Any], Dict[str, Any]]: +def fetch_servers(prod_url: str, dev_url: str) -> tuple[dict[str, Any], dict[str, Any]]: """Fetches the NFe server list from the webpage using pandas and BeautifulSoup.""" servers = {} constants = {} # To store dynamically generated constants @@ -88,15 +88,13 @@ def fetch_servers(prod_url: str, dev_url: str) -> Tuple[Dict[str, Any], Dict[str action_dict[constants[constant_name]] = path # Handle special case for Ambiente Nacional (AN) - if server == "AN": - # Ensure NFeDistribuicaoDFe is included - if "NFeDistribuicaoDFe" in actions: - constant_name = "NFEDISTRIBUICAODFE" - if constant_name not in constants: - constants[constant_name] = "NFeDistribuicaoDFe" - action_dict[constants[constant_name]] = paths[ - actions.index("NFeDistribuicaoDFe") - ] + if server == "AN" and "NFeDistribuicaoDFe" in actions: + constant_name = "NFEDISTRIBUICAODFE" + if constant_name not in constants: + constants[constant_name] = "NFeDistribuicaoDFe" + action_dict[constants[constant_name]] = paths[ + actions.index("NFeDistribuicaoDFe") + ] servers[server] = action_dict @@ -112,7 +110,7 @@ def fetch_servers(prod_url: str, dev_url: str) -> Tuple[Dict[str, Any], Dict[str def save_servers( - servers: Dict[str, Any], constants: Dict[str, str], output_file: Path + servers: dict[str, Any], constants: dict[str, str], output_file: Path ) -> None: """Saves the extracted server data and constants as a Python file.""" # Generate the constants section diff --git a/nfelib/utils/wsdl_downloader.py b/nfelib/utils/wsdl_downloader.py index 4bc74aa1..2a10f739 100644 --- a/nfelib/utils/wsdl_downloader.py +++ b/nfelib/utils/wsdl_downloader.py @@ -23,7 +23,7 @@ } -def download_wsdl_files(*wsdl_urls, generate=False): +def download_wsdl_files(*wsdl_urls: str, generate: bool = False): """Download WSDL files for NF-e, CT-e, MDF-e, and BP-e.""" # Access the certificate and password from environment variables CERT_FILE = environ.get("CERT_FILE") @@ -72,7 +72,7 @@ def download_wsdl_files(*wsdl_urls, generate=False): # Identify the document type (NF-e, CT-e, MDF-e, BP-e) doc_type = None - for key, (_, prefix) in WSDL_DIRS.items(): + for key, (_, _prefix) in WSDL_DIRS.items(): if key in url.lower(): doc_type = key break diff --git a/tests/nfe/test_client.py b/tests/nfe/test_client.py index e9ea2332..ff51b1b4 100644 --- a/tests/nfe/test_client.py +++ b/tests/nfe/test_client.py @@ -1,22 +1,17 @@ import logging -import io -import os import time from os import environ from pathlib import Path from unittest import TestCase, mock from decorator import decorate -from erpbrasil.assinatura import certificado as cert from erpbrasil.assinatura import misc -from nfelib.nfe.bindings.v4_0.nfe_v4_00 import Nfe -from nfelib.nfe.bindings.v4_0.proc_nfe_v4_00 import TnfeProc -from requests import Session -from requests_pkcs12 import Pkcs12Adapter from xsdata.formats.dataclass.parsers import XmlParser from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.transports import DefaultTransport +from nfelib.nfe.bindings.v4_0.nfe_v4_00 import Nfe +from nfelib.nfe.bindings.v4_0.proc_nfe_v4_00 import TnfeProc from nfelib.nfe.client.v4_0 import NfeClient response_status = b""" @@ -113,7 +108,11 @@ def setUpClass(cls): pkcs12_password=cls.cert_password, fake_certificate=cls.fake_certificate, ) - cls.signed_nfe_xml = cls.nfe.to_xml(pkcs12_data=cls.cert_data, pkcs12_password=cls.cert_password, doc_id=cls.nfe.infNFe.Id) + cls.signed_nfe_xml = cls.nfe.to_xml( + pkcs12_data=cls.cert_data, + pkcs12_password=cls.cert_password, + doc_id=cls.nfe.infNFe.Id, + ) @only_if_valid_certificate def test_0_status(self): @@ -131,7 +130,6 @@ def test_1_envia_documento(self): res = self.client.envia_documento([self.signed_nfe_xml]) self.assertEqual(res.cStat, "103") print(res) - import time time.sleep(int(res.infRec.tMed)) res = self.client.consulta_recibo(numero=res.infRec.nRec) @@ -205,4 +203,3 @@ def test_inutilizacao(self): "81583054000129", "55", "1", "2", "2", "Era apenas um teste" ) self.client.envia_inutilizacao(inut_event) - diff --git a/tests/nfe/test_servers.py b/tests/nfe/test_servers.py index 8f30b753..ec6c0d2e 100644 --- a/tests/nfe/test_servers.py +++ b/tests/nfe/test_servers.py @@ -1,4 +1,5 @@ from pathlib import Path + from nfelib.nfe.client.v4_0.servers_scraper import main OUTPUT_FILE = Path("nfelib/nfe/client/v4_0/servers.py") @@ -12,6 +13,6 @@ def test_scraper(): old_content = read_current_servers() main() new_content = read_current_servers() - assert ( - new_content == old_content - ), "Server list has changed. Review and commit the new file." + assert new_content == old_content, ( + "Server list has changed. Review and commit the new file." + ) From 7fdf016250e68ae36d8e932d9337970b8d01fcff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:16:35 +0000 Subject: [PATCH 25/38] don't format scraped servers list --- .pre-commit-config.yaml | 2 ++ nfelib/cte/client/v4_0/servers_scraper.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40d34d93..bc35f257 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,8 @@ exclude: | .*/wsdl/.*\.wsdl$| .*/soap/.*\.py$| nfelib/nfe/ws/edoc_legacy.py| + nfelib/nfe/client/v4_0/servers.py| + nfelib/ctee/client/v4_0/servers.py| ^README\.md$ repos: diff --git a/nfelib/cte/client/v4_0/servers_scraper.py b/nfelib/cte/client/v4_0/servers_scraper.py index ba334b2b..ea582f2a 100644 --- a/nfelib/cte/client/v4_0/servers_scraper.py +++ b/nfelib/cte/client/v4_0/servers_scraper.py @@ -1,4 +1,3 @@ - from pathlib import Path from nfelib.utils.servers_scraper import fetch_servers, save_servers @@ -10,6 +9,7 @@ def main(): + """Cli entry point.""" servers, constants = fetch_servers(PROD_URL, DEV_URL) if servers: save_servers(servers, constants, OUTPUT_FILE) From fdfbaf1af7391d846fd5b6ca1d02465db60a22ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:33:18 +0000 Subject: [PATCH 26/38] py3.8 compat --- nfelib/nfe/client/v4_0/wsdl_downloader.py | 6 +++--- nfelib/utils/servers_scraper.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nfelib/nfe/client/v4_0/wsdl_downloader.py b/nfelib/nfe/client/v4_0/wsdl_downloader.py index c9dcd5d5..bbe04787 100644 --- a/nfelib/nfe/client/v4_0/wsdl_downloader.py +++ b/nfelib/nfe/client/v4_0/wsdl_downloader.py @@ -1,11 +1,11 @@ import logging -import os -import subprocess import sys + from nfelib.utils.wsdl_downloader import download_wsdl_files -def main(): +def main() -> None: + """Cli entry point.""" logging.basicConfig(level=logging.INFO) generate = False diff --git a/nfelib/utils/servers_scraper.py b/nfelib/utils/servers_scraper.py index f555895d..670ce42a 100644 --- a/nfelib/utils/servers_scraper.py +++ b/nfelib/utils/servers_scraper.py @@ -1,5 +1,7 @@ # Copyright (C) 2024 Raphaël Valyi - Akretion +from __future__ import annotations # Python 3.8 compat + import logging from io import StringIO from pathlib import Path From 5e1d42b6939e658ebc7e10d738e6a06f80e74167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:37:16 +0000 Subject: [PATCH 27/38] pre-commit fixes --- nfelib/nfe/client/v4_0/__init__.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/nfelib/nfe/client/v4_0/__init__.py b/nfelib/nfe/client/v4_0/__init__.py index fae1b719..3216a330 100644 --- a/nfelib/nfe/client/v4_0/__init__.py +++ b/nfelib/nfe/client/v4_0/__init__.py @@ -12,6 +12,8 @@ from nfelib.nfe.bindings.v4_0.cons_stat_serv_v4_00 import ConsStatServ from nfelib.nfe.bindings.v4_0.envi_nfe_v4_00 import EnviNfe from nfelib.nfe.bindings.v4_0.inut_nfe_v4_00 import InutNfe +from nfelib.nfe.bindings.v4_0.leiaute_cons_sit_nfe_v4_00 import TconsSitNfeXServ +from nfelib.nfe.bindings.v4_0.leiaute_cons_stat_serv_v4_00 import TconsStatServXServ from nfelib.nfe.bindings.v4_0.leiaute_inut_nfe_v4_00 import TinutNfe from nfelib.nfe.bindings.v4_0.leiaute_nfe_v4_00 import Tnfe from nfelib.nfe.bindings.v4_0.ret_cons_reci_nfe_v4_00 import RetConsReciNfe @@ -19,15 +21,6 @@ from nfelib.nfe.bindings.v4_0.ret_cons_stat_serv_v4_00 import RetConsStatServ from nfelib.nfe.bindings.v4_0.ret_envi_nfe_v4_00 import RetEnviNfe from nfelib.nfe.bindings.v4_0.ret_inut_nfe_v4_00 import RetInutNfe -from nfelib.nfe_evento_cancel.bindings.v1_0.evento_canc_nfe_v1_00 import ( - Tevento as TeventoCancel, -) -from nfelib.nfe_evento_cancel.bindings.v1_0.leiaute_evento_canc_nfe_v1_00 import ( - TenvEvento, - TretEnvEvento, -) -from nfelib.nfe_evento_cce.bindings.v1_0.leiaute_cce_v1_00 import Tevento as TeventoCCe - from nfelib.nfe.soap.v4_0.nfeautorizacao4 import ( NfeAutorizacao4SoapNfeAutorizacaoLote, ) @@ -51,6 +44,14 @@ from nfelib.nfe.soap.v4_0.recepcaoevento4 import ( NfeRecepcaoEvento4SoapNfeRecepcaoEvento, ) +from nfelib.nfe_evento_cancel.bindings.v1_0.evento_canc_nfe_v1_00 import ( + Tevento as TeventoCancel, +) +from nfelib.nfe_evento_cancel.bindings.v1_0.leiaute_evento_canc_nfe_v1_00 import ( + TenvEvento, + TretEnvEvento, +) +from nfelib.nfe_evento_cce.bindings.v1_0.leiaute_cce_v1_00 import Tevento as TeventoCCe # TODO import file generated by test_generate_servers.py instead SERVERS_NFE = { @@ -177,25 +178,25 @@ def send( ###################################### # OK 100% - def status_servico(self, xServ: str = "STATUS") -> RetConsStatServ: + def status_servico(self) -> RetConsStatServ: return self.send( NfeStatusServico4SoapNfeStatusServicoNf, ConsStatServ( tpAmb=self.ambiente, cUF=self.uf, - xServ=xServ, + xServ=TconsStatServXServ(value="STATUS"), versao=self.versao, ), ) # OK 100% - def consulta_documento(self, chave: str, xServ: str = "CONSULTAR") -> RetConsSitNfe: + def consulta_documento(self, chave: str) -> RetConsSitNfe: return self.send( NfeConsultaProtocolo4SoapNfeConsultaNf, ConsSitNfe( versao=self.versao, tpAmb=self.ambiente, - xServ=xServ, + xServ=TconsSitNfeXServ(value="CONSULTAR"), chNFe=chave, ), ) @@ -338,8 +339,7 @@ def cancela_documento( def carta_correcao( self, chave: str, sequencia: str, justificativa: str, data_hora_evento: str = "" ): - """ - Binding details: + """Binding details: nfelib/nfe_evento_cancel/bindings/v1_0/leiaute_evento_canc_nfe_v1_00.py """ return TeventoCCe.InfEvento( From bb0d22cee9ede8cf1036a07fb2ab08ad7a4c3b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:38:52 +0000 Subject: [PATCH 28/38] pre-commit fixes --- nfelib/nfe/client/v4_0/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nfelib/nfe/client/v4_0/__init__.py b/nfelib/nfe/client/v4_0/__init__.py index 3216a330..80a3891a 100644 --- a/nfelib/nfe/client/v4_0/__init__.py +++ b/nfelib/nfe/client/v4_0/__init__.py @@ -310,8 +310,7 @@ def cancela_documento( justificativa: str, data_hora_evento=False, ): - """ - Binding details in: + """Binding details in: nfelib/nfe_evento_cancel/bindings/v1_0/leiaute_evento_canc_nfe_v1_00.py """ tipo_evento = "110111" @@ -371,8 +370,7 @@ def inutilizacao( num_fin: str, justificativa: str, ) -> TinutNfe: - """ - Binding details in: + """Binding details in: nfelib/nfe/bindings/v4_0/leiaute_inut_nfe_v4_00.py """ year = str(date.today().year)[2:] From 3cbe2cbd74ea43bada32fa8a79926f60383f79eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:40:04 +0000 Subject: [PATCH 29/38] fix typo --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc35f257..7ebcfe52 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ exclude: | .*/soap/.*\.py$| nfelib/nfe/ws/edoc_legacy.py| nfelib/nfe/client/v4_0/servers.py| - nfelib/ctee/client/v4_0/servers.py| + nfelib/cte/client/v4_0/servers.py| ^README\.md$ repos: From b0fd1295afffd1a3208af944515c674df3dbe92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:43:00 +0000 Subject: [PATCH 30/38] pre-commit fix --- nfelib/nfe/client/v4_0/servers_scraper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nfelib/nfe/client/v4_0/servers_scraper.py b/nfelib/nfe/client/v4_0/servers_scraper.py index 9f4c61cf..44fc096c 100644 --- a/nfelib/nfe/client/v4_0/servers_scraper.py +++ b/nfelib/nfe/client/v4_0/servers_scraper.py @@ -9,6 +9,7 @@ def main(): + """Cli entry point.""" servers, constants = fetch_servers(PROD_URL, DEV_URL) if servers: save_servers(servers, constants, OUTPUT_FILE) From 509aa087cec769444da1a7ee4794884063faf086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:44:12 +0000 Subject: [PATCH 31/38] consultar cadastro is not a national service anymore --- nfelib/nfe/client/v4_0/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nfelib/nfe/client/v4_0/__init__.py b/nfelib/nfe/client/v4_0/__init__.py index 80a3891a..2a8a8aaa 100644 --- a/nfelib/nfe/client/v4_0/__init__.py +++ b/nfelib/nfe/client/v4_0/__init__.py @@ -437,6 +437,3 @@ def consultar_distribuicao( # self._generateds_to_string_etree(nfe_proc)[0] # proc_recibo.protocolo = protocolo # return True - - def consultar_cadastro(self, uf, cnpj=None, cpf=None, ie=None): - pass From a8516d6dc42cea27f06c278670eab22ec025d01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 03:50:04 +0000 Subject: [PATCH 32/38] add CTe WSDL + SOAP bindings --- .../v4_0/input_cte_tipos_basico_v4_00.py | 441 ++++++++++++++++++ nfelib/cte/bindings/v4_0/input_cte_v4_00.py | 22 + .../cte/bindings/v4_0/proc_input_cte_v4_00.py | 21 + .../cte/bindings/v4_0/ret_input_cte_v4_00.py | 22 + nfelib/cte/client/v4_0/wsdl_downloader.py | 31 ++ nfelib/cte/soap/__init__.py | 6 + nfelib/cte/soap/v4_0/__init__.py | 21 + nfelib/cte/soap/v4_0/cteconsultav4.py | 141 ++++++ nfelib/cte/soap/v4_0/cterecepcaoeventov4.py | 147 ++++++ nfelib/cte/soap/v4_0/cterecepcaogtvev4.py | 139 ++++++ nfelib/cte/soap/v4_0/cterecepcaoosv4.py | 141 ++++++ nfelib/cte/soap/v4_0/cterecepcaosincv4.py | 139 ++++++ nfelib/cte/soap/v4_0/ctestatusservicov4.py | 147 ++++++ nfelib/cte/wsdl/v4_0/cteconsultav4.wsdl | 50 ++ nfelib/cte/wsdl/v4_0/cterecepcaoeventov4.wsdl | 50 ++ nfelib/cte/wsdl/v4_0/cterecepcaogtvev4.wsdl | 44 ++ nfelib/cte/wsdl/v4_0/cterecepcaoosv4.wsdl | 44 ++ nfelib/cte/wsdl/v4_0/cterecepcaosincv4.wsdl | 44 ++ nfelib/cte/wsdl/v4_0/ctestatusservicov4.wsdl | 50 ++ tests/cte/test_servers.py | 18 + 20 files changed, 1718 insertions(+) create mode 100644 nfelib/cte/bindings/v4_0/input_cte_tipos_basico_v4_00.py create mode 100644 nfelib/cte/bindings/v4_0/input_cte_v4_00.py create mode 100644 nfelib/cte/bindings/v4_0/proc_input_cte_v4_00.py create mode 100644 nfelib/cte/bindings/v4_0/ret_input_cte_v4_00.py create mode 100644 nfelib/cte/client/v4_0/wsdl_downloader.py create mode 100644 nfelib/cte/soap/__init__.py create mode 100644 nfelib/cte/soap/v4_0/__init__.py create mode 100644 nfelib/cte/soap/v4_0/cteconsultav4.py create mode 100644 nfelib/cte/soap/v4_0/cterecepcaoeventov4.py create mode 100644 nfelib/cte/soap/v4_0/cterecepcaogtvev4.py create mode 100644 nfelib/cte/soap/v4_0/cterecepcaoosv4.py create mode 100644 nfelib/cte/soap/v4_0/cterecepcaosincv4.py create mode 100644 nfelib/cte/soap/v4_0/ctestatusservicov4.py create mode 100644 nfelib/cte/wsdl/v4_0/cteconsultav4.wsdl create mode 100644 nfelib/cte/wsdl/v4_0/cterecepcaoeventov4.wsdl create mode 100644 nfelib/cte/wsdl/v4_0/cterecepcaogtvev4.wsdl create mode 100644 nfelib/cte/wsdl/v4_0/cterecepcaoosv4.wsdl create mode 100644 nfelib/cte/wsdl/v4_0/cterecepcaosincv4.wsdl create mode 100644 nfelib/cte/wsdl/v4_0/ctestatusservicov4.wsdl create mode 100644 tests/cte/test_servers.py diff --git a/nfelib/cte/bindings/v4_0/input_cte_tipos_basico_v4_00.py b/nfelib/cte/bindings/v4_0/input_cte_tipos_basico_v4_00.py new file mode 100644 index 00000000..1d260774 --- /dev/null +++ b/nfelib/cte/bindings/v4_0/input_cte_tipos_basico_v4_00.py @@ -0,0 +1,441 @@ +"""This file was generated by xsdata, v24.4, on 2024-04-08 21:52:20 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +from dataclasses import dataclass, field +from typing import Optional + +from nfelib import CommonMixin +from nfelib.cte.bindings.v4_0.tipos_geral_cte_v4_00 import ( + Tamb, + TcodUfIbge, + TmodCtCargaOs, +) +from nfelib.cte.bindings.v4_0.xmldsig_core_schema_v1_01 import Signature + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte" + + +@dataclass +class TinutCte(CommonMixin): + """ + Tipo Pedido de Inutilização de Numeração do Conhecimento de Transporte + eletrônico. + + :ivar infInput: Dados do Pedido de Inutilização de Numeração do + Conhecimento de Transporte eletrônico + :ivar signature: + :ivar versao: + """ + + class Meta: + name = "TInputCTe" + + infInput: Optional["TinutCte.InfInput"] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + signature: Optional[Signature] = field( + default=None, + metadata={ + "name": "Signature", + "type": "Element", + "namespace": "http://www.w3.org/2000/09/xmldsig#", + "required": True, + }, + ) + versao: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "pattern": r"4\.00", + }, + ) + + @dataclass + class InfInput(CommonMixin): + """ + :ivar tpAmb: Identificação do Ambiente: 1 - Produção 2 - + Homologação + :ivar xServ: Serviço Solicitado + :ivar cUF: Código da UF solicitada + :ivar ano: Ano de inutilização da numeração + :ivar CNPJ: CNPJ do emitente + :ivar mod: Modelo da CT-e (57 ou 67) + :ivar series: Série da CT-e + :ivar nCTIni: Número da CT-e inicial + :ivar nCTFin: Número da CT-e final + :ivar xJust: Justificativa do pedido de inutilização + :ivar Id: + """ + + tpAmb: Optional[Tamb] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + xServ: str = field( + init=False, + default="INUTILIZAR", + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "white_space": "preserve", + "pattern": r"[!-ÿ]{1}[ -ÿ]{0,}[!-ÿ]{1}|[!-ÿ]{1}", + }, + ) + cUF: Optional[TcodUfIbge] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + ano: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "pattern": r"[0-9]{1,2}", + }, + ) + CNPJ: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "white_space": "preserve", + "pattern": r"[0-9]{14}", + }, + ) + mod: Optional[TmodCtCargaOs] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + series: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "white_space": "preserve", + "pattern": r"0|[1-9]{1}[0-9]{0,2}", + }, + ) + nCTIni: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "white_space": "preserve", + "pattern": r"[1-9]{1}[0-9]{0,8}", + }, + ) + nCTFin: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "white_space": "preserve", + "pattern": r"[1-9]{1}[0-9]{0,8}", + }, + ) + xJust: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "min_length": 15, + "max_length": 255, + "white_space": "preserve", + "pattern": r"[!-ÿ]{1}[ -ÿ]{0,}[!-ÿ]{1}|[!-ÿ]{1}", + }, + ) + Id: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "pattern": r"ID[0-9]{39}", + }, + ) + + +@dataclass +class TretInputCte(CommonMixin): + """ + Tipo retorno do Pedido de Inutilização de Numeração do Conhecimento de + Transporte eletrônico. + + :ivar infInput: Dados do Retorno do Pedido de Inutilização de + Numeração do Conhecimento de Transporte eletrônico + :ivar signature: + :ivar versao: + """ + + class Meta: + name = "TRetInputCTe" + + infInput: Optional["TretInputCte.InfInput"] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + signature: Optional[Signature] = field( + default=None, + metadata={ + "name": "Signature", + "type": "Element", + "namespace": "http://www.w3.org/2000/09/xmldsig#", + }, + ) + versao: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "pattern": r"4\.00", + }, + ) + + @dataclass + class InfInput(CommonMixin): + """ + :ivar tpAmb: Identificação do Ambiente: 1 - Produção 2 - + Homologação + :ivar verAplic: Versão do Aplicativo que processou a CT-e + :ivar cStat: Código do status da mensagem enviada. + :ivar xMotivo: Descrição literal do status do serviço + solicitado. + :ivar cUF: Código da UF solicitada + :ivar ano: Ano de inutilização da numeração + :ivar CNPJ: CNPJ do emitente + :ivar mod: Modelo da CT-e (57 ou 67) + :ivar series: Série da CT-e + :ivar nCTIni: Número da CT-e inicial + :ivar nCTFin: Número da CT-e final + :ivar dhRecbto: Data e hora de recebimento, no formato AAAA-MM- + DDTHH:MM:SS TZD. Deve set preenchida com data e hora da + gravação no Banco em caso de Confirmação. Em caso de + Rejeição, com data e hora do recebimento do Pedido de + Inutilização. + :ivar nProt: Número do Protocolo de Status do CT-e. 1 posição (1 + – Secretaria de Fazenda Estadual , 3 - SEFAZ Virtual RS, 5 - + SEFAZ Virtual SP); 2 - código da UF - 2 posições ano; 10 + seqüencial no ano. + :ivar Id: + """ + + tpAmb: Optional[Tamb] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + verAplic: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "min_length": 1, + "max_length": 20, + "white_space": "preserve", + "pattern": r"[!-ÿ]{1}[ -ÿ]{0,}[!-ÿ]{1}|[!-ÿ]{1}", + }, + ) + cStat: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "white_space": "preserve", + "pattern": r"[0-9]{3}", + }, + ) + xMotivo: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + "min_length": 1, + "max_length": 255, + "white_space": "collapse", + }, + ) + cUF: Optional[TcodUfIbge] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + ano: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "pattern": r"[0-9]{1,2}", + }, + ) + CNPJ: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "white_space": "preserve", + "pattern": r"[0-9]{14}", + }, + ) + mod: Optional[TmodCtCargaOs] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + }, + ) + series: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "white_space": "preserve", + "pattern": r"0|[1-9]{1}[0-9]{0,2}", + }, + ) + nCTIni: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "white_space": "preserve", + "pattern": r"[1-9]{1}[0-9]{0,8}", + }, + ) + nCTFin: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "white_space": "preserve", + "pattern": r"[1-9]{1}[0-9]{0,8}", + }, + ) + dhRecbto: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "white_space": "preserve", + "pattern": r"(((20(([02468][048])|([13579][26]))-02-29))|(20[0-9][0-9])-((((0[1-9])|(1[0-2]))-((0[1-9])|(1\d)|(2[0-8])))|((((0[13578])|(1[02]))-31)|(((0[1,3-9])|(1[0-2]))-(29|30)))))T(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d([\-,\+](0[0-9]|10|11):00|([\+](12):00))", + }, + ) + nProt: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "white_space": "preserve", + "pattern": r"[0-9]{15}", + }, + ) + Id: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + }, + ) + + +@dataclass +class TprocInputCte(CommonMixin): + """ + Tipo Pedido de inutilzação de númeração de CT-e processado. + + :ivar inputCTe: + :ivar retInputCTe: + :ivar versao: + :ivar ipTransmissor: IP do transmissor do documento fiscal para o + ambiente autorizador + :ivar nPortaCon: Porta de origem utilizada na conexão (De 0 a 65535) + :ivar dhConexao: Data e Hora da Conexão de Origem + """ + + class Meta: + name = "TProcInputCTe" + + inputCTe: Optional[TinutCte] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + retInputCTe: Optional[TretInputCte] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte", + "required": True, + }, + ) + versao: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "pattern": r"4\.00", + }, + ) + ipTransmissor: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "white_space": "preserve", + "pattern": r"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])", + }, + ) + nPortaCon: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "pattern": r"[0-9]{1,5}", + }, + ) + dhConexao: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "white_space": "preserve", + "pattern": r"(((20(([02468][048])|([13579][26]))-02-29))|(20[0-9][0-9])-((((0[1-9])|(1[0-2]))-((0[1-9])|(1\d)|(2[0-8])))|((((0[13578])|(1[02]))-31)|(((0[1,3-9])|(1[0-2]))-(29|30)))))T(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d([\-,\+](0[0-9]|10|11):00|([\+](12):00))", + }, + ) diff --git a/nfelib/cte/bindings/v4_0/input_cte_v4_00.py b/nfelib/cte/bindings/v4_0/input_cte_v4_00.py new file mode 100644 index 00000000..a3f9fa4c --- /dev/null +++ b/nfelib/cte/bindings/v4_0/input_cte_v4_00.py @@ -0,0 +1,22 @@ +"""This file was generated by xsdata, v24.4, on 2024-04-08 21:52:20 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +from dataclasses import dataclass + +from nfelib.cte.bindings.v4_0.input_cte_tipos_basico_v4_00 import TinutCte + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte" + + +@dataclass +class InputCte(TinutCte): + """ + Schema XML de validação do Pedido de Inutilização de Numeração do Conhecimento + de Transportes eletrônico. + """ + + class Meta: + name = "inputCTe" + namespace = "http://www.portalfiscal.inf.br/cte" diff --git a/nfelib/cte/bindings/v4_0/proc_input_cte_v4_00.py b/nfelib/cte/bindings/v4_0/proc_input_cte_v4_00.py new file mode 100644 index 00000000..584599c8 --- /dev/null +++ b/nfelib/cte/bindings/v4_0/proc_input_cte_v4_00.py @@ -0,0 +1,21 @@ +"""This file was generated by xsdata, v24.4, on 2024-04-08 21:52:20 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +from dataclasses import dataclass + +from nfelib.cte.bindings.v4_0.input_cte_tipos_basico_v4_00 import TprocInputCte + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte" + + +@dataclass +class ProcInputCte(TprocInputCte): + """ + Pedido de inutilzação de numeração de CT-e processado. + """ + + class Meta: + name = "procInputCTe" + namespace = "http://www.portalfiscal.inf.br/cte" diff --git a/nfelib/cte/bindings/v4_0/ret_input_cte_v4_00.py b/nfelib/cte/bindings/v4_0/ret_input_cte_v4_00.py new file mode 100644 index 00000000..2629f28d --- /dev/null +++ b/nfelib/cte/bindings/v4_0/ret_input_cte_v4_00.py @@ -0,0 +1,22 @@ +"""This file was generated by xsdata, v24.4, on 2024-04-08 21:52:20 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +from dataclasses import dataclass + +from nfelib.cte.bindings.v4_0.input_cte_tipos_basico_v4_00 import TretInputCte + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte" + + +@dataclass +class RetInputCte(TretInputCte): + """ + Schema XML de validação do retorno do Pedido de Inutilização de Numeração do + CT-e. + """ + + class Meta: + name = "retInputCTe" + namespace = "http://www.portalfiscal.inf.br/cte" diff --git a/nfelib/cte/client/v4_0/wsdl_downloader.py b/nfelib/cte/client/v4_0/wsdl_downloader.py new file mode 100644 index 00000000..eab1265a --- /dev/null +++ b/nfelib/cte/client/v4_0/wsdl_downloader.py @@ -0,0 +1,31 @@ +import logging +import sys + +from nfelib.utils.wsdl_downloader import download_wsdl_files + + +def main() -> None: + """Cli entry point.""" + logging.basicConfig(level=logging.INFO) + + generate = False + if "--generate" in sys.argv: + generate = True + sys.argv.remove( + "--generate" + ) # Remove the --generate flag to avoid interfering with argparse + + # Call the generic download_wsdl_files method with CTe-specific URLs + download_wsdl_files( + "https://cte.svrs.rs.gov.br/ws/CTeStatusServicoV4/CTeStatusServicoV4.asmx", + "https://cte.svrs.rs.gov.br/ws/CTeConsultaV4/CTeConsultaV4.asmx", + "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoSincV4/CTeRecepcaoSincV4.asmx", + "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoOSV4/CTeRecepcaoOSV4.asmx", + "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoGTVeV4/CTeRecepcaoGTVeV4.asmx", + "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoEventoV4/CTeRecepcaoEventoV4.asmx", + generate=generate, + ) + + +if __name__ == "__main__": + main() diff --git a/nfelib/cte/soap/__init__.py b/nfelib/cte/soap/__init__.py new file mode 100644 index 00000000..38c4c486 --- /dev/null +++ b/nfelib/cte/soap/__init__.py @@ -0,0 +1,6 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:18 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +# nothing here diff --git a/nfelib/cte/soap/v4_0/__init__.py b/nfelib/cte/soap/v4_0/__init__.py new file mode 100644 index 00000000..f4e73fd4 --- /dev/null +++ b/nfelib/cte/soap/v4_0/__init__.py @@ -0,0 +1,21 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:23 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from nfelib.cte.soap.v4_0.cterecepcaoeventov4 import ( + CteRecepcaoEventoV4Soap12CteRecepcaoEvento, + CteRecepcaoEventoV4Soap12CteRecepcaoEventoInput, + CteRecepcaoEventoV4Soap12CteRecepcaoEventoOutput, + CteDadosMsg, + CteRecepcaoEventoResult, +) + +__all__ = [ + "CteRecepcaoEventoV4Soap12CteRecepcaoEvento", + "CteRecepcaoEventoV4Soap12CteRecepcaoEventoInput", + "CteRecepcaoEventoV4Soap12CteRecepcaoEventoOutput", + "CteDadosMsg", + "CteRecepcaoEventoResult", +] diff --git a/nfelib/cte/soap/v4_0/cteconsultav4.py b/nfelib/cte/soap/v4_0/cteconsultav4.py new file mode 100644 index 00000000..377e1521 --- /dev/null +++ b/nfelib/cte/soap/v4_0/cteconsultav4.py @@ -0,0 +1,141 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:19 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte/wsdl/CTeConsultaV4" + + +@dataclass +class CteConsultaCtresult(CommonMixin): + class Meta: + name = "cteConsultaCTResult" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeConsultaV4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteDadosMsg(CommonMixin): + class Meta: + name = "cteDadosMsg" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeConsultaV4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteConsultaV4Soap12CteConsultaCtInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteConsultaV4Soap12CteConsultaCtInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + cteDadosMsg: Optional[CteDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeConsultaV4", + }, + ) + + +@dataclass +class CteConsultaV4Soap12CteConsultaCtOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteConsultaV4Soap12CteConsultaCtOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + cteConsultaCTResult: Optional[CteConsultaCtresult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeConsultaV4", + }, + ) + Fault: Optional[ + "CteConsultaV4Soap12CteConsultaCtOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class CteConsultaV4Soap12CteConsultaCt: + style = "document" + location = "https://cte.svrs.rs.gov.br/ws/CTeConsultaV4/CTeConsultaV4.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = ( + "http://www.portalfiscal.inf.br/cte/wsdl/CTeConsultaV4/cteConsultaCT" + ) + input = CteConsultaV4Soap12CteConsultaCtInput + output = CteConsultaV4Soap12CteConsultaCtOutput diff --git a/nfelib/cte/soap/v4_0/cterecepcaoeventov4.py b/nfelib/cte/soap/v4_0/cterecepcaoeventov4.py new file mode 100644 index 00000000..9c3ee623 --- /dev/null +++ b/nfelib/cte/soap/v4_0/cterecepcaoeventov4.py @@ -0,0 +1,147 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:23 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoEventoV4" + + +@dataclass +class CteDadosMsg(CommonMixin): + class Meta: + name = "cteDadosMsg" + namespace = ( + "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoEventoV4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteRecepcaoEventoResult(CommonMixin): + class Meta: + name = "cteRecepcaoEventoResult" + namespace = ( + "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoEventoV4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteRecepcaoEventoV4Soap12CteRecepcaoEventoInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoEventoV4Soap12CteRecepcaoEventoInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + cteDadosMsg: Optional[CteDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoEventoV4", + }, + ) + + +@dataclass +class CteRecepcaoEventoV4Soap12CteRecepcaoEventoOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoEventoV4Soap12CteRecepcaoEventoOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + cteRecepcaoEventoResult: Optional[CteRecepcaoEventoResult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoEventoV4", + }, + ) + Fault: Optional[ + "CteRecepcaoEventoV4Soap12CteRecepcaoEventoOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class CteRecepcaoEventoV4Soap12CteRecepcaoEvento: + style = "document" + location = "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoEventoV4/CTeRecepcaoEventoV4.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoEventoV4/cteRecepcaoEvento" + input = CteRecepcaoEventoV4Soap12CteRecepcaoEventoInput + output = CteRecepcaoEventoV4Soap12CteRecepcaoEventoOutput diff --git a/nfelib/cte/soap/v4_0/cterecepcaogtvev4.py b/nfelib/cte/soap/v4_0/cterecepcaogtvev4.py new file mode 100644 index 00000000..5283e6ad --- /dev/null +++ b/nfelib/cte/soap/v4_0/cterecepcaogtvev4.py @@ -0,0 +1,139 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:22 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoGTVeV4" + + +@dataclass +class CteRecepcaoGtveV4Soap12CteRecepcaoGtveInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoGtveV4Soap12CteRecepcaoGtveInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + cteDadosMsg: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoGTVeV4", + }, + ) + + +@dataclass +class CteDadosMsg(CommonMixin): + class Meta: + name = "cteDadosMsg" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoGTVeV4" + + value: str = field( + default="", + metadata={ + "required": True, + }, + ) + + +@dataclass +class CteRecepcaoGtveResult(CommonMixin): + class Meta: + name = "cteRecepcaoGTVeResult" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoGTVeV4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteRecepcaoGtveV4Soap12CteRecepcaoGtveOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoGtveV4Soap12CteRecepcaoGtveOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + cteRecepcaoGTVeResult: Optional[CteRecepcaoGtveResult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoGTVeV4", + }, + ) + Fault: Optional[ + "CteRecepcaoGtveV4Soap12CteRecepcaoGtveOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class CteRecepcaoGtveV4Soap12CteRecepcaoGtve: + style = "document" + location = "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoGTVeV4/CTeRecepcaoGTVeV4.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoGTVeV4/cteRecepcaoGTVe" + input = CteRecepcaoGtveV4Soap12CteRecepcaoGtveInput + output = CteRecepcaoGtveV4Soap12CteRecepcaoGtveOutput diff --git a/nfelib/cte/soap/v4_0/cterecepcaoosv4.py b/nfelib/cte/soap/v4_0/cterecepcaoosv4.py new file mode 100644 index 00000000..328a6113 --- /dev/null +++ b/nfelib/cte/soap/v4_0/cterecepcaoosv4.py @@ -0,0 +1,141 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:21 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoOSV4" + + +@dataclass +class CteRecepcaoOsv4Soap12CteRecepcaoOsInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoOsv4Soap12CteRecepcaoOsInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + cteDadosMsg: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoOSV4", + }, + ) + + +@dataclass +class CteDadosMsg(CommonMixin): + class Meta: + name = "cteDadosMsg" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoOSV4" + + value: str = field( + default="", + metadata={ + "required": True, + }, + ) + + +@dataclass +class CteRecepcaoOsresult(CommonMixin): + class Meta: + name = "cteRecepcaoOSResult" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoOSV4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteRecepcaoOsv4Soap12CteRecepcaoOsOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoOsv4Soap12CteRecepcaoOsOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + cteRecepcaoOSResult: Optional[CteRecepcaoOsresult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoOSV4", + }, + ) + Fault: Optional[ + "CteRecepcaoOsv4Soap12CteRecepcaoOsOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class CteRecepcaoOsv4Soap12CteRecepcaoOs: + style = "document" + location = ( + "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoOSV4/CTeRecepcaoOSV4.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = ( + "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoOSV4/cteRecepcaoOS" + ) + input = CteRecepcaoOsv4Soap12CteRecepcaoOsInput + output = CteRecepcaoOsv4Soap12CteRecepcaoOsOutput diff --git a/nfelib/cte/soap/v4_0/cterecepcaosincv4.py b/nfelib/cte/soap/v4_0/cterecepcaosincv4.py new file mode 100644 index 00000000..a986adc1 --- /dev/null +++ b/nfelib/cte/soap/v4_0/cterecepcaosincv4.py @@ -0,0 +1,139 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:20 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoSincV4" + + +@dataclass +class CteRecepcaoSincV4Soap12CteRecepcaoInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoSincV4Soap12CteRecepcaoInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + cteDadosMsg: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoSincV4", + }, + ) + + +@dataclass +class CteDadosMsg(CommonMixin): + class Meta: + name = "cteDadosMsg" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoSincV4" + + value: str = field( + default="", + metadata={ + "required": True, + }, + ) + + +@dataclass +class CteRecepcaoResult(CommonMixin): + class Meta: + name = "cteRecepcaoResult" + namespace = "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoSincV4" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteRecepcaoSincV4Soap12CteRecepcaoOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteRecepcaoSincV4Soap12CteRecepcaoOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + cteRecepcaoResult: Optional[CteRecepcaoResult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoSincV4", + }, + ) + Fault: Optional[ + "CteRecepcaoSincV4Soap12CteRecepcaoOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class CteRecepcaoSincV4Soap12CteRecepcao: + style = "document" + location = "https://cte.svrs.rs.gov.br/ws/CTeRecepcaoSincV4/CTeRecepcaoSincV4.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = ( + "http://www.portalfiscal.inf.br/cte/wsdl/CTeRecepcaoSincV4/cteRecepcao" + ) + input = CteRecepcaoSincV4Soap12CteRecepcaoInput + output = CteRecepcaoSincV4Soap12CteRecepcaoOutput diff --git a/nfelib/cte/soap/v4_0/ctestatusservicov4.py b/nfelib/cte/soap/v4_0/ctestatusservicov4.py new file mode 100644 index 00000000..88007f59 --- /dev/null +++ b/nfelib/cte/soap/v4_0/ctestatusservicov4.py @@ -0,0 +1,147 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:11:18 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/cte/wsdl/CTeStatusServicoV4" + + +@dataclass +class CteDadosMsg(CommonMixin): + class Meta: + name = "cteDadosMsg" + namespace = ( + "http://www.portalfiscal.inf.br/cte/wsdl/CTeStatusServicoV4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteStatusServicoCtresult(CommonMixin): + class Meta: + name = "cteStatusServicoCTResult" + namespace = ( + "http://www.portalfiscal.inf.br/cte/wsdl/CTeStatusServicoV4" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class CteStatusServicoV4Soap12CteStatusServicoCtInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteStatusServicoV4Soap12CteStatusServicoCtInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + cteDadosMsg: Optional[CteDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeStatusServicoV4", + }, + ) + + +@dataclass +class CteStatusServicoV4Soap12CteStatusServicoCtOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["CteStatusServicoV4Soap12CteStatusServicoCtOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + cteStatusServicoCTResult: Optional[CteStatusServicoCtresult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/cte/wsdl/CTeStatusServicoV4", + }, + ) + Fault: Optional[ + "CteStatusServicoV4Soap12CteStatusServicoCtOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class CteStatusServicoV4Soap12CteStatusServicoCt: + style = "document" + location = "https://cte.svrs.rs.gov.br/ws/CTeStatusServicoV4/CTeStatusServicoV4.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/cte/wsdl/CTeStatusServicoV4/cteStatusServicoCT" + input = CteStatusServicoV4Soap12CteStatusServicoCtInput + output = CteStatusServicoV4Soap12CteStatusServicoCtOutput diff --git a/nfelib/cte/wsdl/v4_0/cteconsultav4.wsdl b/nfelib/cte/wsdl/v4_0/cteconsultav4.wsdl new file mode 100644 index 00000000..5c2a142f --- /dev/null +++ b/nfelib/cte/wsdl/v4_0/cteconsultav4.wsdl @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/cte/wsdl/v4_0/cterecepcaoeventov4.wsdl b/nfelib/cte/wsdl/v4_0/cterecepcaoeventov4.wsdl new file mode 100644 index 00000000..209e02a7 --- /dev/null +++ b/nfelib/cte/wsdl/v4_0/cterecepcaoeventov4.wsdl @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/cte/wsdl/v4_0/cterecepcaogtvev4.wsdl b/nfelib/cte/wsdl/v4_0/cterecepcaogtvev4.wsdl new file mode 100644 index 00000000..325e782f --- /dev/null +++ b/nfelib/cte/wsdl/v4_0/cterecepcaogtvev4.wsdl @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/cte/wsdl/v4_0/cterecepcaoosv4.wsdl b/nfelib/cte/wsdl/v4_0/cterecepcaoosv4.wsdl new file mode 100644 index 00000000..b5e8f1b7 --- /dev/null +++ b/nfelib/cte/wsdl/v4_0/cterecepcaoosv4.wsdl @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/cte/wsdl/v4_0/cterecepcaosincv4.wsdl b/nfelib/cte/wsdl/v4_0/cterecepcaosincv4.wsdl new file mode 100644 index 00000000..1e642f13 --- /dev/null +++ b/nfelib/cte/wsdl/v4_0/cterecepcaosincv4.wsdl @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/cte/wsdl/v4_0/ctestatusservicov4.wsdl b/nfelib/cte/wsdl/v4_0/ctestatusservicov4.wsdl new file mode 100644 index 00000000..fd1c71ed --- /dev/null +++ b/nfelib/cte/wsdl/v4_0/ctestatusservicov4.wsdl @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/cte/test_servers.py b/tests/cte/test_servers.py new file mode 100644 index 00000000..c48c0e90 --- /dev/null +++ b/tests/cte/test_servers.py @@ -0,0 +1,18 @@ +from pathlib import Path + +from nfelib.nfe.client.v4_0.servers_scraper import main + +OUTPUT_FILE = Path("nfelib/cte/client/v4_0/servers.py") + + +def read_current_servers(): + return OUTPUT_FILE.read_text(encoding="utf-8") if OUTPUT_FILE.exists() else "" + + +def test_scraper(): + old_content = read_current_servers() + main() + new_content = read_current_servers() + assert new_content == old_content, ( + "Server list has changed. Review and commit the new file." + ) From 2f03dda60be09f0ad3333ccfcb5cae01640ffb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 12:17:08 +0000 Subject: [PATCH 33/38] don't try to lint MDF-e scraped servers --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7ebcfe52..5909684d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,7 @@ exclude: | nfelib/nfe/ws/edoc_legacy.py| nfelib/nfe/client/v4_0/servers.py| nfelib/cte/client/v4_0/servers.py| + nfelib/mdfe/client/v3_0/servers.py| ^README\.md$ repos: From b1eeffea12bd32f4962766d89e914590c766cd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 12:20:21 +0000 Subject: [PATCH 34/38] add MDF-e servers and servers_scraper --- nfelib/mdfe/client/v3_0/servers.py | 51 ++++++++ nfelib/mdfe/client/v3_0/servers_scraper.py | 130 +++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 nfelib/mdfe/client/v3_0/servers.py create mode 100644 nfelib/mdfe/client/v3_0/servers_scraper.py diff --git a/nfelib/mdfe/client/v3_0/servers.py b/nfelib/mdfe/client/v3_0/servers.py new file mode 100644 index 00000000..d42103be --- /dev/null +++ b/nfelib/mdfe/client/v3_0/servers.py @@ -0,0 +1,51 @@ +# Auto-generated file. Do not edit manually. + +# Constants +MDFERECEPCAOEVENTO = "MDFeRecepcaoEvento" +MDFECONSULTA = "MDFeConsulta" +MDFESTATUSSERVICO = "MDFeStatusServico" +MDFECONSNAOENC = "MDFeConsNaoEnc" +MDFEDISTRIBUICAODFE = "MDFeDistribuicaoDFe" +MDFERECEPCAOSINC = "MDFeRecepcaoSinc" +QRCODE = "QR Code" + +# Servers + + +servers = { + 'SVRS': { + 'prod_server': 'mdfe.svrs.rs.gov.br', + 'dev_server': 'mdfe-homologacao.svrs.rs.gov.br', + 'services': { + 'MDFeRecepcaoEvento': { + 'prod': '/ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx', + 'dev': '/ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx', + }, + 'MDFeConsulta': { + 'prod': '/ws/MDFeConsulta/MDFeConsulta.asmx', + 'dev': '/ws/MDFeConsulta/MDFeConsulta.asmx', + }, + 'MDFeStatusServico': { + 'prod': '/ws/MDFeStatusServico/MDFeStatusServico.asmx', + 'dev': '/ws/MDFeStatusServico/MDFeStatusServico.asmx', + }, + 'MDFeConsNaoEnc': { + 'prod': '/ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx', + 'dev': '/ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx', + }, + 'MDFeDistribuicaoDFe': { + 'prod': '/ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx', + 'dev': '/ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx', + }, + 'MDFeRecepcaoSinc': { + 'prod': '/ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx', + 'dev': '/ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx', + }, + 'QR Code': { + 'prod': '/mdfe/qrCode', + 'dev': '/mdfe/qrCode', + }, + }, + }, +} + diff --git a/nfelib/mdfe/client/v3_0/servers_scraper.py b/nfelib/mdfe/client/v3_0/servers_scraper.py new file mode 100644 index 00000000..5f785702 --- /dev/null +++ b/nfelib/mdfe/client/v3_0/servers_scraper.py @@ -0,0 +1,130 @@ +# Copyright (C) 2024 Raphaël Valyi - Akretion + +from __future__ import annotations # Python 3.8 compat + +import logging +from pathlib import Path +from typing import Any + +import requests +from bs4 import BeautifulSoup +from xsdata.formats.dataclass.serializers import PycodeSerializer + +# Constants +MDFE_SVRS_URL = "https://dfe-portal.svrs.rs.gov.br/Mdfe/Servicos" +OUTPUT_FILE = Path("nfelib/nfe/client/servers_mdfe.py") + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def fetch_mdfe_servers(url: str) -> dict[Any, Any]: + """Fetches the MDFe server actions for production and homologation.""" + try: + # Fetch the page content + response = requests.get(url) + response.raise_for_status() # Raise an exception for HTTP errors + + # Parse the HTML content + soup = BeautifulSoup(response.content, "lxml") + + # Initialize dictionaries to store server actions + servers: dict[Any, Any] = { + "SVRS": { + "prod_server": "mdfe.svrs.rs.gov.br", + "dev_server": "mdfe-homologacao.svrs.rs.gov.br", + "services": {}, + } + } + + # Find all tables with server information + tables = soup.find_all("table") + for table in tables: + # Determine if the table is for production or homologation + caption = table.find("caption") + if not caption: + continue + + if "Produção" in caption.text: + environment = "prod" + elif "Homologação" in caption.text: + environment = "dev" + else: + continue + + # Extract server actions from the table + rows = table.find_all("tr")[1:] # Skip the header row + for row in rows: + cols = row.find_all("td") + if len(cols) < 4: # Ensure there are enough columns + continue + + service_name = cols[1].text.strip() + service_url = cols[3].text.strip() + + # Add the service to the servers dictionary + if service_name not in servers["SVRS"]["services"]: + servers["SVRS"]["services"][service_name] = {} + + servers["SVRS"]["services"][service_name][environment] = "/" + "/".join( + service_url.split("/")[3:] + ) + + logger.info("Successfully fetched MDFe servers.") + return servers + + except requests.RequestException as e: + logger.error(f"Failed to fetch MDFe servers: {e}") + return {} + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") + return {} + + +def save_servers(servers: dict[str, Any], output_file: Path) -> None: + """Saves the extracted server data and constants as a Python file.""" + # Generate the constants section + constants = { + "MDFERECEPCAOEVENTO": "MDFeRecepcaoEvento", + "MDFECONSULTA": "MDFeConsulta", + "MDFESTATUSSERVICO": "MDFeStatusServico", + "MDFECONSNAOENC": "MDFeConsNaoEnc", + "MDFEDISTRIBUICAODFE": "MDFeDistribuicaoDFe", + "MDFERECEPCAOSINC": "MDFeRecepcaoSinc", + "QRCODE": "QR Code", + } + constants_section = "\n".join( + [f'{key} = "{value}"' for key, value in constants.items()] + ) + + # Use PycodeSerializer to format the servers dictionary + serializer = PycodeSerializer() + formatted_servers = serializer.render(servers, var_name="servers") + + # Write the formatted output to the file + content = f"""# Auto-generated file. Do not edit manually. + +# Constants +{constants_section} + +# Servers +{formatted_servers} +""" + output_file.parent.mkdir(parents=True, exist_ok=True) + output_file.write_text(content, encoding="utf-8") + logger.info(f"MDFe servers and constants saved to {output_file}") + + +def main(): + """Cli entry point.""" + # Fetch the MDFe server actions + servers = fetch_mdfe_servers(MDFE_SVRS_URL) + + # Save the results to a file + if servers: + save_servers(servers, OUTPUT_FILE) + + +if __name__ == "__main__": + main() From b584fa83339616f07b790a0fa866328ff7d32ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 16:18:09 +0000 Subject: [PATCH 35/38] add MDF-e WSDL downloader and files --- nfelib/mdfe/client/v3_0/wsdl_downloader.py | 32 ++++++++++ nfelib/mdfe/wsdl/v3_0/mdfeconsnaoenc.wsdl | 63 +++++++++++++++++++ nfelib/mdfe/wsdl/v3_0/mdfeconsulta.wsdl | 63 +++++++++++++++++++ .../mdfe/wsdl/v3_0/mdfedistribuicaodfe.wsdl | 63 +++++++++++++++++++ nfelib/mdfe/wsdl/v3_0/mdferecepcaoevento.wsdl | 63 +++++++++++++++++++ nfelib/mdfe/wsdl/v3_0/mdferecepcaosinc.wsdl | 44 +++++++++++++ nfelib/mdfe/wsdl/v3_0/qrcode | 51 +++++++++++++++ 7 files changed, 379 insertions(+) create mode 100644 nfelib/mdfe/client/v3_0/wsdl_downloader.py create mode 100644 nfelib/mdfe/wsdl/v3_0/mdfeconsnaoenc.wsdl create mode 100644 nfelib/mdfe/wsdl/v3_0/mdfeconsulta.wsdl create mode 100644 nfelib/mdfe/wsdl/v3_0/mdfedistribuicaodfe.wsdl create mode 100644 nfelib/mdfe/wsdl/v3_0/mdferecepcaoevento.wsdl create mode 100644 nfelib/mdfe/wsdl/v3_0/mdferecepcaosinc.wsdl create mode 100644 nfelib/mdfe/wsdl/v3_0/qrcode diff --git a/nfelib/mdfe/client/v3_0/wsdl_downloader.py b/nfelib/mdfe/client/v3_0/wsdl_downloader.py new file mode 100644 index 00000000..3689d104 --- /dev/null +++ b/nfelib/mdfe/client/v3_0/wsdl_downloader.py @@ -0,0 +1,32 @@ +import logging +import sys + +from nfelib.utils.wsdl_downloader import download_wsdl_files + + +def main() -> None: + """Cli entry point.""" + logging.basicConfig(level=logging.INFO) + + generate = False + if "--generate" in sys.argv: + generate = True + sys.argv.remove( + "--generate" + ) # Remove the --generate flag to avoid interfering with argparse + + # Call the generic download_wsdl_files method with MDFe-specific URLs + download_wsdl_files( + "https://mdfe.svrs.rs.gov.br/ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx", + "https://mdfe.svrs.rs.gov.br/ws/MDFeConsulta/MDFeConsulta.asmx", + "https://mdfe.svrs.rs.gov.br/ws/MDFeConsulta/MDFeConsulta.asmx", + "https://mdfe.svrs.rs.gov.br/ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx", + "https://mdfe.svrs.rs.gov.br/ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx", + "https://mdfe.svrs.rs.gov.br/ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx", + # "https://dfe-portal.svrs.rs.gov.br/Mdfe/QrCode", # not a wsdl! + generate=generate, + ) + + +if __name__ == "__main__": + main() diff --git a/nfelib/mdfe/wsdl/v3_0/mdfeconsnaoenc.wsdl b/nfelib/mdfe/wsdl/v3_0/mdfeconsnaoenc.wsdl new file mode 100644 index 00000000..806a4132 --- /dev/null +++ b/nfelib/mdfe/wsdl/v3_0/mdfeconsnaoenc.wsdl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/mdfe/wsdl/v3_0/mdfeconsulta.wsdl b/nfelib/mdfe/wsdl/v3_0/mdfeconsulta.wsdl new file mode 100644 index 00000000..c78cae5a --- /dev/null +++ b/nfelib/mdfe/wsdl/v3_0/mdfeconsulta.wsdl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/mdfe/wsdl/v3_0/mdfedistribuicaodfe.wsdl b/nfelib/mdfe/wsdl/v3_0/mdfedistribuicaodfe.wsdl new file mode 100644 index 00000000..c7a249de --- /dev/null +++ b/nfelib/mdfe/wsdl/v3_0/mdfedistribuicaodfe.wsdl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/mdfe/wsdl/v3_0/mdferecepcaoevento.wsdl b/nfelib/mdfe/wsdl/v3_0/mdferecepcaoevento.wsdl new file mode 100644 index 00000000..fc772873 --- /dev/null +++ b/nfelib/mdfe/wsdl/v3_0/mdferecepcaoevento.wsdl @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/mdfe/wsdl/v3_0/mdferecepcaosinc.wsdl b/nfelib/mdfe/wsdl/v3_0/mdferecepcaosinc.wsdl new file mode 100644 index 00000000..bc3f76ce --- /dev/null +++ b/nfelib/mdfe/wsdl/v3_0/mdferecepcaosinc.wsdl @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nfelib/mdfe/wsdl/v3_0/qrcode b/nfelib/mdfe/wsdl/v3_0/qrcode new file mode 100644 index 00000000..8d1082fb --- /dev/null +++ b/nfelib/mdfe/wsdl/v3_0/qrcode @@ -0,0 +1,51 @@ + + + + + + + + + Portal dos Documentos Fiscais Eletrônicos - SVRS + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file From f7b7f8596f43bd99ba58d94b1b42be8e97444d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 16:19:08 +0000 Subject: [PATCH 36/38] add MDFe SOAP bindings --- nfelib/mdfe/soap/__init__.py | 6 + nfelib/mdfe/soap/v3_0/__init__.py | 21 ++ nfelib/mdfe/soap/v3_0/mdfeconsnaoenc.py | 202 +++++++++++++++++ nfelib/mdfe/soap/v3_0/mdfeconsulta.py | 200 +++++++++++++++++ nfelib/mdfe/soap/v3_0/mdfedistribuicaodfe.py | 214 +++++++++++++++++++ nfelib/mdfe/soap/v3_0/mdferecepcaoevento.py | 212 ++++++++++++++++++ nfelib/mdfe/soap/v3_0/mdferecepcaosinc.py | 139 ++++++++++++ 7 files changed, 994 insertions(+) create mode 100644 nfelib/mdfe/soap/__init__.py create mode 100644 nfelib/mdfe/soap/v3_0/__init__.py create mode 100644 nfelib/mdfe/soap/v3_0/mdfeconsnaoenc.py create mode 100644 nfelib/mdfe/soap/v3_0/mdfeconsulta.py create mode 100644 nfelib/mdfe/soap/v3_0/mdfedistribuicaodfe.py create mode 100644 nfelib/mdfe/soap/v3_0/mdferecepcaoevento.py create mode 100644 nfelib/mdfe/soap/v3_0/mdferecepcaosinc.py diff --git a/nfelib/mdfe/soap/__init__.py b/nfelib/mdfe/soap/__init__.py new file mode 100644 index 00000000..160a213d --- /dev/null +++ b/nfelib/mdfe/soap/__init__.py @@ -0,0 +1,6 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:09:49 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +# nothing here diff --git a/nfelib/mdfe/soap/v3_0/__init__.py b/nfelib/mdfe/soap/v3_0/__init__.py new file mode 100644 index 00000000..d5a9aa9c --- /dev/null +++ b/nfelib/mdfe/soap/v3_0/__init__.py @@ -0,0 +1,21 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:18:28 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from nfelib.mdfe.soap.v3_0.mdferecepcaosinc import ( + MdfeRecepcaoSincSoap12MdfeRecepcao, + MdfeRecepcaoSincSoap12MdfeRecepcaoInput, + MdfeRecepcaoSincSoap12MdfeRecepcaoOutput, + MdfeDadosMsg, + MdfeRecepcaoResult, +) + +__all__ = [ + "MdfeRecepcaoSincSoap12MdfeRecepcao", + "MdfeRecepcaoSincSoap12MdfeRecepcaoInput", + "MdfeRecepcaoSincSoap12MdfeRecepcaoOutput", + "MdfeDadosMsg", + "MdfeRecepcaoResult", +] diff --git a/nfelib/mdfe/soap/v3_0/mdfeconsnaoenc.py b/nfelib/mdfe/soap/v3_0/mdfeconsnaoenc.py new file mode 100644 index 00000000..5bc93c9e --- /dev/null +++ b/nfelib/mdfe/soap/v3_0/mdfeconsnaoenc.py @@ -0,0 +1,202 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:18:26 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import Dict, List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc" + + +@dataclass +class MdfeCabecMsg(CommonMixin): + class Meta: + name = "mdfeCabecMsg" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc" + + cUF: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + versaoDados: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + any_attributes: Dict[str, str] = field( + default_factory=dict, + metadata={ + "type": "Attributes", + "namespace": "##any", + }, + ) + + +@dataclass +class MdfeConsNaoEncResult(CommonMixin): + class Meta: + name = "mdfeConsNaoEncResult" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeDadosMsg(CommonMixin): + class Meta: + name = "mdfeDadosMsg" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeConsNaoEncSoap12MdfeConsNaoEncInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeConsNaoEncSoap12MdfeConsNaoEncInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + Header: Optional["MdfeConsNaoEncSoap12MdfeConsNaoEncInput.Header"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeDadosMsg: Optional[MdfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc", + }, + ) + + +@dataclass +class MdfeConsNaoEncSoap12MdfeConsNaoEncOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeConsNaoEncSoap12MdfeConsNaoEncOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + Header: Optional["MdfeConsNaoEncSoap12MdfeConsNaoEncOutput.Header"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + + @dataclass + class Body(CommonMixin): + mdfeConsNaoEncResult: Optional[MdfeConsNaoEncResult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc", + }, + ) + Fault: Optional[ + "MdfeConsNaoEncSoap12MdfeConsNaoEncOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc", + }, + ) + + +class MdfeConsNaoEncSoap12MdfeConsNaoEnc: + style = "document" + location = ( + "https://mdfe.svrs.rs.gov.br/ws/MDFeConsNaoEnc/MDFeConsNaoEnc.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsNaoEnc/mdfeConsNaoEnc" + input = MdfeConsNaoEncSoap12MdfeConsNaoEncInput + output = MdfeConsNaoEncSoap12MdfeConsNaoEncOutput diff --git a/nfelib/mdfe/soap/v3_0/mdfeconsulta.py b/nfelib/mdfe/soap/v3_0/mdfeconsulta.py new file mode 100644 index 00000000..5ff9bb2d --- /dev/null +++ b/nfelib/mdfe/soap/v3_0/mdfeconsulta.py @@ -0,0 +1,200 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:18:25 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import Dict, List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta" + + +@dataclass +class MdfeCabecMsg(CommonMixin): + class Meta: + name = "mdfeCabecMsg" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta" + + cUF: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + versaoDados: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + any_attributes: Dict[str, str] = field( + default_factory=dict, + metadata={ + "type": "Attributes", + "namespace": "##any", + }, + ) + + +@dataclass +class MdfeConsultaMdfresult(CommonMixin): + class Meta: + name = "mdfeConsultaMDFResult" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeDadosMsg(CommonMixin): + class Meta: + name = "mdfeDadosMsg" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeConsultaSoap12MdfeConsultaMdfInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeConsultaSoap12MdfeConsultaMdfInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + Header: Optional["MdfeConsultaSoap12MdfeConsultaMdfInput.Header"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeDadosMsg: Optional[MdfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta", + }, + ) + + +@dataclass +class MdfeConsultaSoap12MdfeConsultaMdfOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeConsultaSoap12MdfeConsultaMdfOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + Header: Optional["MdfeConsultaSoap12MdfeConsultaMdfOutput.Header"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeConsultaMDFResult: Optional[MdfeConsultaMdfresult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta", + }, + ) + Fault: Optional[ + "MdfeConsultaSoap12MdfeConsultaMdfOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta", + }, + ) + + +class MdfeConsultaSoap12MdfeConsultaMdf: + style = "document" + location = "https://mdfe.svrs.rs.gov.br/ws/MDFeConsulta/MDFeConsulta.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = ( + "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeConsulta/mdfeConsultaMDF" + ) + input = MdfeConsultaSoap12MdfeConsultaMdfInput + output = MdfeConsultaSoap12MdfeConsultaMdfOutput diff --git a/nfelib/mdfe/soap/v3_0/mdfedistribuicaodfe.py b/nfelib/mdfe/soap/v3_0/mdfedistribuicaodfe.py new file mode 100644 index 00000000..e16dc7b1 --- /dev/null +++ b/nfelib/mdfe/soap/v3_0/mdfedistribuicaodfe.py @@ -0,0 +1,214 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:18:27 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import Dict, List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe" + + +@dataclass +class MdfeCabecMsg(CommonMixin): + class Meta: + name = "mdfeCabecMsg" + namespace = ( + "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe" + ) + + cUF: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + versaoDados: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + any_attributes: Dict[str, str] = field( + default_factory=dict, + metadata={ + "type": "Attributes", + "namespace": "##any", + }, + ) + + +@dataclass +class MdfeDadosMsg(CommonMixin): + class Meta: + name = "mdfeDadosMsg" + namespace = ( + "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeDistDfeInteresseResult(CommonMixin): + class Meta: + name = "mdfeDistDFeInteresseResult" + namespace = ( + "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional[ + "MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseInput.Body" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + Header: Optional[ + "MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseInput.Header" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeDadosMsg: Optional[MdfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe", + }, + ) + + +@dataclass +class MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional[ + "MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseOutput.Body" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + Header: Optional[ + "MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseOutput.Header" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeDistDFeInteresseResult: Optional[MdfeDistDfeInteresseResult] = ( + field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe", + }, + ) + ) + Fault: Optional[ + "MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe", + }, + ) + + +class MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresse: + style = "document" + location = "https://mdfe.svrs.rs.gov.br/ws/MDFeDistribuicaoDFe/MDFeDistribuicaoDFe.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeDistribuicaoDFe/mdfeDistDFeInteresse" + input = MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseInput + output = MdfeDistribuicaoDfeSoap12MdfeDistDfeInteresseOutput diff --git a/nfelib/mdfe/soap/v3_0/mdferecepcaoevento.py b/nfelib/mdfe/soap/v3_0/mdferecepcaoevento.py new file mode 100644 index 00000000..fbf6df3a --- /dev/null +++ b/nfelib/mdfe/soap/v3_0/mdferecepcaoevento.py @@ -0,0 +1,212 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:18:22 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import Dict, List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento" + + +@dataclass +class MdfeCabecMsg(CommonMixin): + class Meta: + name = "mdfeCabecMsg" + namespace = ( + "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento" + ) + + cUF: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + versaoDados: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + any_attributes: Dict[str, str] = field( + default_factory=dict, + metadata={ + "type": "Attributes", + "namespace": "##any", + }, + ) + + +@dataclass +class MdfeDadosMsg(CommonMixin): + class Meta: + name = "mdfeDadosMsg" + namespace = ( + "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeRecepcaoEventoResult(CommonMixin): + class Meta: + name = "mdfeRecepcaoEventoResult" + namespace = ( + "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento" + ) + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoInput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + Header: Optional[ + "MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoInput.Header" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeDadosMsg: Optional[MdfeDadosMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento", + }, + ) + + +@dataclass +class MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoOutput.Body"] = ( + field( + default=None, + metadata={ + "type": "Element", + }, + ) + ) + Header: Optional[ + "MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoOutput.Header" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeRecepcaoEventoResult: Optional[MdfeRecepcaoEventoResult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento", + }, + ) + Fault: Optional[ + "MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + @dataclass + class Header(CommonMixin): + mdfeCabecMsg: Optional[MdfeCabecMsg] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento", + }, + ) + + +class MdfeRecepcaoEventoSoap12MdfeRecepcaoEvento: + style = "document" + location = "https://mdfe.svrs.rs.gov.br/ws/MDFeRecepcaoEvento/MDFeRecepcaoEvento.asmx" + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoEvento/mdfeRecepcaoEvento" + input = MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoInput + output = MdfeRecepcaoEventoSoap12MdfeRecepcaoEventoOutput diff --git a/nfelib/mdfe/soap/v3_0/mdferecepcaosinc.py b/nfelib/mdfe/soap/v3_0/mdferecepcaosinc.py new file mode 100644 index 00000000..3150e72a --- /dev/null +++ b/nfelib/mdfe/soap/v3_0/mdferecepcaosinc.py @@ -0,0 +1,139 @@ +"""This file was generated by xsdata, v24.2.1, on 2025-03-24 16:18:28 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" + +from dataclasses import dataclass, field +from nfelib import CommonMixin +from typing import List, Optional + +__NAMESPACE__ = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoSinc" + + +@dataclass +class MdfeRecepcaoSincSoap12MdfeRecepcaoInput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeRecepcaoSincSoap12MdfeRecepcaoInput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeDadosMsg: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoSinc", + }, + ) + + +@dataclass +class MdfeDadosMsg(CommonMixin): + class Meta: + name = "mdfeDadosMsg" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoSinc" + + value: str = field( + default="", + metadata={ + "required": True, + }, + ) + + +@dataclass +class MdfeRecepcaoResult(CommonMixin): + class Meta: + name = "mdfeRecepcaoResult" + namespace = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoSinc" + + content: List[object] = field( + default_factory=list, + metadata={ + "type": "Wildcard", + "namespace": "##any", + "mixed": True, + }, + ) + + +@dataclass +class MdfeRecepcaoSincSoap12MdfeRecepcaoOutput(CommonMixin): + class Meta: + name = "Envelope" + namespace = "http://schemas.xmlsoap.org/soap/envelope/" + + Body: Optional["MdfeRecepcaoSincSoap12MdfeRecepcaoOutput.Body"] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Body(CommonMixin): + mdfeRecepcaoResult: Optional[MdfeRecepcaoResult] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoSinc", + }, + ) + Fault: Optional[ + "MdfeRecepcaoSincSoap12MdfeRecepcaoOutput.Body.Fault" + ] = field( + default=None, + metadata={ + "type": "Element", + }, + ) + + @dataclass + class Fault(CommonMixin): + faultcode: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultstring: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + faultactor: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + detail: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + "namespace": "", + }, + ) + + +class MdfeRecepcaoSincSoap12MdfeRecepcao: + style = "document" + location = ( + "https://mdfe.svrs.rs.gov.br/ws/MDFeRecepcaoSinc/MDFeRecepcaoSinc.asmx" + ) + transport = "http://schemas.xmlsoap.org/soap/http" + soapAction = "http://www.portalfiscal.inf.br/mdfe/wsdl/MDFeRecepcaoSinc/mdfeRecepcao" + input = MdfeRecepcaoSincSoap12MdfeRecepcaoInput + output = MdfeRecepcaoSincSoap12MdfeRecepcaoOutput From e0467cb4d62ca3811fe5505ba2d16829edcd8c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Mon, 24 Mar 2025 18:07:51 +0000 Subject: [PATCH 37/38] optional [soap] dependencies --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1a70b406..d9d48e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,11 @@ sign = [ pdf = [ "brazilfiscalreport", ] +soap = [ + "xsdata[soap]", + "erpbrasil.assinatura", + "brazil-fiscal-client", +] test = [ "pre-commit", "pytest", From d6803bf14ba6988fdaa9d285b7938b12948689ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Tue, 25 Mar 2025 20:57:46 +0000 Subject: [PATCH 38/38] import NFe servers --- nfelib/nfe/client/v4_0/__init__.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/nfelib/nfe/client/v4_0/__init__.py b/nfelib/nfe/client/v4_0/__init__.py index 2a8a8aaa..a191c3b7 100644 --- a/nfelib/nfe/client/v4_0/__init__.py +++ b/nfelib/nfe/client/v4_0/__init__.py @@ -53,21 +53,7 @@ ) from nfelib.nfe_evento_cce.bindings.v1_0.leiaute_cce_v1_00 import Tevento as TeventoCCe -# TODO import file generated by test_generate_servers.py instead -SERVERS_NFE = { - "AM": {}, # ... - "SVRS": { - "prod_server": "nfe.svrs.rs.gov.br", - "dev_server": "nfe-homologacao.svrs.rs.gov.br", - "NfeInutilizacao": "/ws/nfeinutilizacao/nfeinutilizacao4.asmx", - "NfeConsultaProtocolo": "/ws/NfeConsulta/NfeConsulta4.asmx", - "NfeStatusServico": "/ws/NfeStatusServico/NfeStatusServico4.asmx", - "NfeConsultaCadastro": "/ws/cadconsultacadastro/cadconsultacadastro4.asmx", - "RecepcaoEvento": "/ws/recepcaoevento/recepcaoevento4.asmx", - "NFeAutorizacao": "/ws/NfeAutorizacao/NFeAutorizacao4.asmx", - "NFeRetAutorizacao": "/ws/NfeRetAutorizacao/NFeRetAutorizacao4.asmx", - }, # ... -} +from nfelib.nfe.client.v4_0.servers import servers as SERVERS_NFE UF_TO_SERVER = { # NOTE this is fixed compared to erpbrasil.edoc