diff --git a/Notas-calamo.py b/Notas-calamo.py new file mode 100644 index 0000000..58d712c --- /dev/null +++ b/Notas-calamo.py @@ -0,0 +1,780 @@ +import requests +import pandas as pd +import json +import os +import time +import pyodbc +import base64 +import xml.etree.ElementTree as ET +from datetime import datetime, timedelta, timezone +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + +# ============================================================================= +# CONFIGURAÇÕES +# ============================================================================= + +# Configurações da API +API_URL = "https://api.arquivei.com.br/v1/nfe/received" +API_HEADERS = { + "X-API-ID": "3e51eeaeb4c678bb648801cbc545da9cc75682cf", + "X-API-KEY": "73d6941b0c948ac010b35c4f57506072dac44a4f", + "Content-Type": "application/json" +} + +# CNPJs permitidos para filtragem +CNPJS_PERMITIDOS = {'06147451000990', '06147451000809'} + +# Quantidade de dias para buscar (hoje + X dias para trás) +DIAS_PARA_BUSCAR = 1 # Altere aqui para mudar o período + +# ============================================================================= +# FUNÇÕES AUXILIARES +# ============================================================================= + +def create_session_with_retry(): + """Cria uma sessão de requests com configuração de retry e timeout""" + session = requests.Session() + + try: + retry_strategy = Retry( + total=3, + status_forcelist=[429, 500, 502, 503, 504], + allowed_methods=["HEAD", "GET", "OPTIONS"], + backoff_factor=1 + ) + except TypeError: + try: + retry_strategy = Retry( + total=3, + status_forcelist=[429, 500, 502, 503, 504], + method_whitelist=["HEAD", "GET", "OPTIONS"], + backoff_factor=1 + ) + except TypeError: + retry_strategy = Retry( + total=3, + status_forcelist=[429, 500, 502, 503, 504], + backoff_factor=1 + ) + + adapter = HTTPAdapter(max_retries=retry_strategy) + session.mount("http://", adapter) + session.mount("https://", adapter) + + return session + +def get_db_connection(): + """Configuração da conexão SQL Server""" + return pyodbc.connect( + 'DRIVER={FreeTDS};' + 'SERVER=10.77.77.10;' + 'DATABASE=GINSENG;' + 'UID=supginseng;' + 'PWD=Iphone2513@;' + 'PORT=1433;' + 'TrustServerCertificate=yes' + ) + +def fazer_requisicao_robusta(session, url, headers, params, max_tentativas=3): + """Faz uma requisição HTTP com tratamento robusto de timeout e erros""" + for tentativa in range(max_tentativas): + try: + print(f" Tentativa {tentativa + 1}/{max_tentativas}...", end=" ", flush=True) + response = session.get(url, headers=headers, params=params, timeout=(30, 60)) + + if response.status_code == 200: + print("Sucesso!") + return response + else: + print(f"Erro {response.status_code}") + if tentativa < max_tentativas - 1: + wait_time = (tentativa + 1) * 2 + print(f" Aguardando {wait_time} segundos...") + time.sleep(wait_time) + + except requests.exceptions.ConnectTimeout: + print("Timeout de conexão!") + if tentativa < max_tentativas - 1: + time.sleep((tentativa + 1) * 5) + + except requests.exceptions.ReadTimeout: + print("Timeout de leitura!") + if tentativa < max_tentativas - 1: + time.sleep((tentativa + 1) * 3) + + except requests.exceptions.ConnectionError as e: + print(f"Erro de conexão: {str(e)[:100]}...") + if tentativa < max_tentativas - 1: + time.sleep((tentativa + 1) * 5) + + except Exception as e: + print(f"Erro inesperado: {str(e)[:100]}...") + if tentativa < max_tentativas - 1: + time.sleep((tentativa + 1) * 2) + + print(" Todas as tentativas falharam") + return None + +# ============================================================================= +# PARTE 1: BUSCAR NOTAS E IDENTIFICAR NOVAS CHAVES +# ============================================================================= + +def consultar_chaves_existentes(chaves_list): + """Consulta quais chaves já existem no banco de dados""" + if not chaves_list: + return set() + + try: + conn = get_db_connection() + cursor = conn.cursor() + + chaves_str = "', '".join(chaves_list) + query = f""" + SELECT DISTINCT [chave] + FROM [GINSENG].[dbo].[fato_notas_entrada] + WHERE [chave] IN ('{chaves_str}') + """ + + cursor.execute(query) + chaves_existentes = {row[0] for row in cursor.fetchall()} + + cursor.close() + conn.close() + + print(f" Consulta no banco: {len(chaves_existentes):,} chaves já existem de {len(chaves_list):,} consultadas") + return chaves_existentes + + except Exception as e: + print(f" Erro ao consultar banco de dados: {e}") + return set() + +def inserir_nfes_banco(registros_novos): + """Insere os registros novos de NFe no banco de dados""" + if not registros_novos: + return 0 + + try: + conn = get_db_connection() + cursor = conn.cursor() + + insert_query = """ + INSERT INTO [GINSENG].[dbo].[fato_notas_entrada] ( + [chave], [cnf], [serie], [data_emissao], [hora_emissao], [cnpj_emissor], + [nome_emissor], [cnpj_destinatario], [valor_total_produtos], [valor_icmsst], + [valor_fcpst], [valor_frete], [valor_seguro], [valor_outras_despesas], + [valor_ii], [valor_ipi], [valor_ipi_devol], [valor_servicos], [valor_desconto], + [valor_icms_desonerado], [valor_liquido], [tipo_pagamento_json], [numero_fatura], + [qtd_parcelas], [duplicatas_json], [valor_icms], [situacao], [TRIAL119] + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """ + + registros_inseridos = 0 + for registro in registros_novos: + try: + cursor.execute(insert_query, ( + registro.get('chave', ''), + registro.get('cnf', ''), + registro.get('serie', ''), + registro.get('data_emissao', None), + registro.get('hora_emissao', None), + registro.get('cnpj_emissor', ''), + registro.get('nome_emissor', ''), + registro.get('cnpj_destinatario', ''), + float(registro.get('valor_total_produtos', 0)) if registro.get('valor_total_produtos') else None, + float(registro.get('valor_icmsst', 0)) if registro.get('valor_icmsst') else None, + float(registro.get('valor_fcpst', 0)) if registro.get('valor_fcpst') else None, + float(registro.get('valor_frete', 0)) if registro.get('valor_frete') else None, + float(registro.get('valor_seguro', 0)) if registro.get('valor_seguro') else None, + float(registro.get('valor_outras_despesas', 0)) if registro.get('valor_outras_despesas') else None, + float(registro.get('valor_ii', 0)) if registro.get('valor_ii') else None, + float(registro.get('valor_ipi', 0)) if registro.get('valor_ipi') else None, + float(registro.get('valor_ipi_devol', 0)) if registro.get('valor_ipi_devol') else None, + float(registro.get('valor_servicos', 0)) if registro.get('valor_servicos') else None, + float(registro.get('valor_desconto', 0)) if registro.get('valor_desconto') else None, + float(registro.get('valor_icms_desonerado', 0)) if registro.get('valor_icms_desonerado') else None, + float(registro.get('valor_liquido', 0)) if registro.get('valor_liquido') else None, + registro.get('tipo_pagamento_json', ''), + registro.get('numero_fatura', ''), + int(registro.get('qtd_parcelas', 0)) if registro.get('qtd_parcelas') else None, + registro.get('duplicatas_json', ''), + float(registro.get('valor_icms', 0)) if registro.get('valor_icms') else None, + registro.get('situacao', ''), + registro.get('TRIAL119', '') + )) + registros_inseridos += 1 + except Exception as e: + print(f" Erro ao inserir registro {registro.get('chave', '')}: {e}") + + conn.commit() + cursor.close() + conn.close() + + return registros_inseridos + + except Exception as e: + print(f" Erro ao conectar com banco de dados: {e}") + return 0 + +def obter_total_registros_dia(session, data_str): + """Consulta quantos registros existem em um dia específico""" + params = { + "created_at[from]": data_str, + "created_at[to]": data_str, + "format_type": "JSON", + "limit": 1 + } + + response = fazer_requisicao_robusta(session, API_URL, API_HEADERS, params, max_tentativas=2) + + if response and response.status_code == 200: + data = response.json() + return data.get('count', 0) + return 0 + +def processar_dia_chaves(session, data_str, all_extracted_data): + """Processa todos os registros de um dia específico para extrair chaves""" + params = { + "created_at[from]": data_str, + "created_at[to]": data_str, + "format_type": "JSON", + "limit": 50 + } + + registros_dia = 0 + registros_filtrados = 0 + page_count = 0 + + while True: + page_count += 1 + print(f" Página {page_count}...", end=" ") + + response = fazer_requisicao_robusta(session, API_URL, API_HEADERS, params, max_tentativas=3) + + if not response or response.status_code != 200: + print(f"Falha") + break + + data = response.json() + nfe_list = data.get('data', []) + + if not nfe_list: + print(f"Fim dos dados") + break + + print(f"{len(nfe_list)} registros") + + for nfe_data in nfe_list: + registros_dia += 1 + + nfe = nfe_data.get('xml', {}).get('NFe', {}) + inf_nfe = nfe.get('infNFe', {}) + ide = inf_nfe.get('ide', {}) + emit = inf_nfe.get('emit', {}) + dest = inf_nfe.get('dest', {}) + total = inf_nfe.get('total', {}).get('ICMSTot', {}) + cobr = inf_nfe.get('cobr', {}) + + cnpj_emissor = str(emit.get('CNPJ', '')).strip() + if cnpj_emissor not in CNPJS_PERMITIDOS: + continue + + registros_filtrados += 1 + + registro = { + 'chave': str(nfe_data.get('access_key', '')).strip(), + 'cnf': str(ide.get('cNF', '')).strip(), + 'serie': str(ide.get('serie', '')).strip(), + 'data_emissao': ide.get('dhEmi', '').split('T')[0] if ide.get('dhEmi') else '', + 'hora_emissao': ide.get('dhEmi', '').split('T')[1].split('-')[0] if ide.get('dhEmi') and 'T' in ide.get('dhEmi', '') else '', + 'cnpj_emissor': cnpj_emissor, + 'nome_emissor': str(emit.get('xNome', '')).strip(), + 'cnpj_destinatario': str(dest.get('CNPJ', '')).strip(), + 'valor_total_produtos': str(total.get('vProd', '')).strip(), + 'valor_icmsst': str(total.get('vST', '')).strip(), + 'valor_fcpst': str(total.get('vFCPST', '')).strip(), + 'valor_frete': str(total.get('vFrete', '')).strip(), + 'valor_seguro': str(total.get('vSeg', '')).strip(), + 'valor_outras_despesas': str(total.get('vOutro', '')).strip(), + 'valor_ii': str(total.get('vII', '')).strip(), + 'valor_ipi': str(total.get('vIPI', '')).strip(), + 'valor_ipi_devol': str(total.get('vIPIDevol', '')).strip(), + 'valor_servicos': '', + 'valor_desconto': str(total.get('vDesc', '')).strip(), + 'valor_icms_desonerado': str(total.get('vICMSDeson', '')).strip(), + 'valor_liquido': str(total.get('vNF', '')).strip(), + 'tipo_pagamento_json': json.dumps(inf_nfe.get('pag', {}), ensure_ascii=False) if inf_nfe.get('pag') else '', + 'numero_fatura': str(cobr.get('fat', {}).get('nFat', '')).strip() if cobr.get('fat') else '', + 'qtd_parcelas': len(cobr.get('dup', [])) if cobr.get('dup') else 0, + 'duplicatas_json': json.dumps(cobr.get('dup', []), ensure_ascii=False) if cobr.get('dup') else '', + 'valor_icms': str(total.get('vICMS', '')).strip(), + 'situacao': str(inf_nfe.get('protNFe', {}).get('infProt', {}).get('cStat', '')).strip() if inf_nfe.get('protNFe') else '' + } + + all_extracted_data.append(registro) + + page_info = data.get('page', {}) + next_url = page_info.get('next') + + if not next_url: + break + + if 'cursor=' in next_url: + cursor_value = next_url.split('cursor=')[1].split('&')[0] + params['cursor'] = cursor_value + else: + break + + time.sleep(0.3) + + return registros_filtrados + +def buscar_notas_e_identificar_novas(): + """ + PARTE 1: Busca notas no período e retorna lista de chaves novas + Retorna: lista de chaves que não existem no banco + """ + print("\n" + "=" * 70) + print(" PARTE 1: BUSCANDO NOTAS E IDENTIFICANDO CHAVES NOVAS") + print("=" * 70) + + data_atual = datetime.now() + data_inicio = data_atual - timedelta(days=DIAS_PARA_BUSCAR) + + print(f" Período: {data_inicio.strftime('%Y-%m-%d')} até {data_atual.strftime('%Y-%m-%d')}") + print(f" CNPJs permitidos: {', '.join(CNPJS_PERMITIDOS)}") + + session = create_session_with_retry() + + # Analisar dias com dados + print(f"\n Analisando período por dia...") + detalhes_dias = [] + current_date = data_inicio + + while current_date <= data_atual: + data_str = current_date.strftime("%Y-%m-%d") + print(f" Consultando {data_str}...", end=" ") + registros = obter_total_registros_dia(session, data_str) + if registros > 0: + detalhes_dias.append((data_str, registros)) + print(f"{registros:,} registros") + else: + print("0 registros") + current_date += timedelta(days=1) + time.sleep(0.3) + + if not detalhes_dias: + print(" Nenhum registro encontrado no período.") + return [] + + # Extrair dados + print(f"\n Extraindo dados dos dias com registros...") + all_extracted_data = [] + + for dia, qtd in detalhes_dias: + print(f"\n Processando {dia} ({qtd:,} registros esperados)") + processar_dia_chaves(session, dia, all_extracted_data) + + print(f"\n Total extraído (após filtro CNPJ): {len(all_extracted_data):,} registros") + + if not all_extracted_data: + print(" Nenhum dado extraído após filtro de CNPJ.") + return [] + + # Verificar duplicatas + print(f"\n Verificando duplicatas no banco...") + chaves_extraidas = [r['chave'] for r in all_extracted_data if r['chave']] + chaves_existentes = consultar_chaves_existentes(chaves_extraidas) + + # Filtrar registros novos + registros_novos = [r for r in all_extracted_data if r['chave'] not in chaves_existentes] + + print(f" Registros duplicados: {len(all_extracted_data) - len(registros_novos):,}") + print(f" Registros novos: {len(registros_novos):,}") + + if not registros_novos: + print(" Todos os registros já existem no banco.") + return [] + + # Inserir no banco fato_notas_entrada + print(f"\n Inserindo {len(registros_novos):,} notas no banco...") + inseridos = inserir_nfes_banco(registros_novos) + print(f" {inseridos:,} notas inseridas com sucesso!") + + # Retornar lista de chaves novas + chaves_novas = [r['chave'] for r in registros_novos if r['chave']] + return chaves_novas + +# ============================================================================= +# PARTE 2: BUSCAR ITENS DAS NOTAS NOVAS +# ============================================================================= + +def buscar_valor_xml(item_element, campo): + """Busca um valor específico dentro de um elemento XML do item da NFe""" + try: + prod = item_element.find('prod') + imposto = item_element.find('imposto') + + if campo in ['cProd', 'cEAN', 'xProd', 'NCM', 'CEST', 'CFOP', 'uCom', 'qCom', 'vUnCom', 'vProd', 'vFrete', 'vSeg', 'vDesc', 'vOutro', 'xPed']: + if prod is not None: + elem = prod.find(campo) + if elem is not None and elem.text: + valor = elem.text.strip() + if campo == 'qCom': + try: + valor_float = float(valor) + if valor_float == int(valor_float): + valor = str(int(valor_float)) + else: + valor = str(valor_float).rstrip('0').rstrip('.') + except: + pass + return valor + return '' + + elif campo in ['orig', 'CST', 'modBC', 'vBC', 'pICMS', 'vICMS', 'vBCFCP', 'pFCP', 'vFCP', 'modBCST', 'pMVAST', 'vBCST', 'pICMSST', 'vICMSST', 'vBCFCPST', 'pFCPST', 'vFCPST', 'vICMSDeson']: + if imposto is not None: + icms = imposto.find('ICMS') + if icms is not None: + for icms_child in icms: + elem = icms_child.find(campo) + if elem is not None and elem.text: + valor = elem.text.strip() + if campo in ['pICMS', 'pICMSST', 'pFCP', 'pFCPST', 'pMVAST']: + try: + valor_float = float(valor) + valor = str(round(valor_float / 100, 4)) + except: + pass + return valor + return '' + + elif campo == 'vII': + if imposto is not None: + ii = imposto.find('II') + if ii is not None: + elem = ii.find('vII') + if elem is not None and elem.text: + return elem.text.strip() + return '' + + elif campo == 'vIPI': + if imposto is not None: + ipi = imposto.find('IPI') + if ipi is not None: + for ipi_child in ipi: + elem = ipi_child.find('vIPI') + if elem is not None and elem.text: + return elem.text.strip() + return '' + + return '' + except: + return '' + +def inserir_itens_banco(lista_itens): + """Insere os itens da NFe no banco de dados""" + if not lista_itens: + return 0 + + try: + conn = get_db_connection() + cursor = conn.cursor() + + try: + cursor.execute("SELECT TOP 0 * FROM [GINSENG].[dbo].[fato_notas_entrada_itens]") + colunas_tabela = [desc[0].lower() for desc in cursor.description] + colunas_tabela = [col for col in colunas_tabela if col not in ['id']] + except Exception as e: + print(f" Erro ao verificar estrutura da tabela: {e}") + colunas_tabela = ['chave', 'n_item', 'cod_produto', 'produto', 'quantidade', 'valor_unitario', 'valor_total_produtos'] + + campos_mapeados = { + 'chave': 'chave', + 'n_item': 'n_item', + 'data_emissao': 'data_emissao', + 'cod_produto': 'cod_produto', + 'produto': 'produto', + 'cEAN': 'cean', + 'NCM': 'ncm', + 'CEST': 'cest', + 'CFOP': 'cfop', + 'unidade_medida': 'unidade_medida', + 'quantidade': 'quantidade', + 'valor_unitario': 'valor_unitario', + 'valor_total_produtos': 'valor_total_produtos', + 'valor_frete': 'valor_frete', + 'valor_seguro': 'valor_seguro', + 'valor_desconto': 'valor_desconto', + 'valor_outras_despesas': 'valor_outras_despesas', + 'codigo_pedido': 'codigo_pedido', + 'cod_origem': 'cod_origem', + 'CST': 'cst', + 'modalidade_BC_ICMS': 'modalidade_bc_icms', + 'valor_BC_ICMS': 'valor_bc_icms', + 'aliquota_ICMS': 'aliquota_icms', + 'valor_ICMS': 'valor_icms', + 'valor_BC_FCP': 'valor_bc_fcp', + 'aliquota_FCP': 'aliquota_fcp', + 'valor_FCP': 'valor_fcp', + 'modalidade_BC_ST': 'modalidade_bc_st', + 'aliquota_MVA_ST': 'aliquota_mva_st', + 'valor_BC_ST': 'valor_bc_st', + 'aliquota_ICMS_ST': 'aliquota_icms_st', + 'valor_ICMSST': 'valor_icmsst', + 'valor_BC_FCPST': 'valor_bc_fcpst', + 'aliquota_FCPST': 'aliquota_fcpst', + 'valor_FCPST': 'valor_fcpst', + 'valor_II': 'valor_ii', + 'valor_IPI': 'valor_ipi', + 'valor_ICMS_desonerado': 'valor_icms_desonerado' + } + + campos_validos = [] + valores_placeholders = [] + + for campo_item, campo_tabela in campos_mapeados.items(): + if campo_tabela in colunas_tabela: + campos_validos.append(f"[{campo_tabela}]") + valores_placeholders.append("?") + + if not campos_validos: + print(" Nenhum campo válido encontrado para inserção!") + return 0 + + insert_query = f""" + INSERT INTO [GINSENG].[dbo].[fato_notas_entrada_itens] ({', '.join(campos_validos)}) + VALUES ({', '.join(valores_placeholders)}) + """ + + registros_inseridos = 0 + for item in lista_itens: + try: + valores = [] + for campo_item, campo_tabela in campos_mapeados.items(): + if campo_tabela in colunas_tabela: + valor = item.get(campo_item, '') + if valor == '': + valor = None + valores.append(valor) + + cursor.execute(insert_query, valores) + registros_inseridos += 1 + except Exception as e: + print(f" Erro ao inserir item {item.get('n_item', '')}: {e}") + + conn.commit() + cursor.close() + conn.close() + + return registros_inseridos + + except Exception as e: + print(f" Erro ao conectar com banco de dados: {e}") + return 0 + +def buscar_itens_das_chaves(chaves_novas): + """ + PARTE 2: Busca os itens das notas novas via API + """ + print("\n" + "=" * 70) + print(" PARTE 2: BUSCANDO ITENS DAS NOTAS NOVAS") + print("=" * 70) + + if not chaves_novas: + print(" Nenhuma chave nova para processar.") + return 0 + + print(f" Total de chaves para buscar itens: {len(chaves_novas):,}") + + session = create_session_with_retry() + lista_itens_notas = [] + chaves_processadas = 0 + chaves_com_erro = 0 + + for i, chave in enumerate(chaves_novas, 1): + print(f"\n [{i}/{len(chaves_novas)}] Processando: {chave[:20]}...") + + url = f"{API_URL}?access_key[]={chave}" + + try: + response = fazer_requisicao_robusta(session, url, API_HEADERS, None, max_tentativas=3) + + if response and response.status_code == 200: + data = response.json() + nfe_list = data.get('data', []) + + if not nfe_list: + print(f" Nenhum dado retornado") + continue + + for nfe_data in nfe_list: + chave_retornada = nfe_data.get('access_key', '') + + xml_base64 = nfe_data.get('xml', '') + if not xml_base64 or not isinstance(xml_base64, str): + print(f" XML não encontrado") + continue + + try: + xml_decoded = base64.b64decode(xml_base64).decode('utf-8') + xml_decoded = xml_decoded.replace('xmlns="http://www.portalfiscal.inf.br/nfe"', '') + root = ET.fromstring(xml_decoded) + except Exception as e: + print(f" Erro ao decodificar XML: {e}") + continue + + # Buscar data de emissão + data_emissao = '' + dhEmi_elem = root.find('.//ide/dhEmi') + if dhEmi_elem is not None and dhEmi_elem.text: + data_emissao = dhEmi_elem.text[:10] + + # Buscar todos os itens + itens = root.findall('.//det') + print(f" {len(itens)} itens encontrados (Emissão: {data_emissao})") + + for j, item in enumerate(itens, 1): + n_item = item.get('nItem', str(j)) + item_data = { + 'chave': chave_retornada, + 'n_item': n_item, + 'data_emissao': data_emissao, + 'cod_produto': buscar_valor_xml(item, 'cProd'), + 'produto': buscar_valor_xml(item, 'xProd'), + 'cEAN': buscar_valor_xml(item, 'cEAN'), + 'NCM': buscar_valor_xml(item, 'NCM'), + 'CEST': buscar_valor_xml(item, 'CEST'), + 'CFOP': buscar_valor_xml(item, 'CFOP'), + 'unidade_medida': buscar_valor_xml(item, 'uCom'), + 'quantidade': buscar_valor_xml(item, 'qCom'), + 'valor_unitario': buscar_valor_xml(item, 'vUnCom'), + 'valor_total_produtos': buscar_valor_xml(item, 'vProd'), + 'valor_frete': buscar_valor_xml(item, 'vFrete'), + 'valor_seguro': buscar_valor_xml(item, 'vSeg'), + 'valor_desconto': buscar_valor_xml(item, 'vDesc'), + 'valor_outras_despesas': buscar_valor_xml(item, 'vOutro'), + 'codigo_pedido': buscar_valor_xml(item, 'xPed'), + 'cod_origem': buscar_valor_xml(item, 'orig'), + 'CST': buscar_valor_xml(item, 'CST'), + 'modalidade_BC_ICMS': buscar_valor_xml(item, 'modBC'), + 'valor_BC_ICMS': buscar_valor_xml(item, 'vBC'), + 'aliquota_ICMS': buscar_valor_xml(item, 'pICMS'), + 'valor_ICMS': buscar_valor_xml(item, 'vICMS'), + 'valor_BC_FCP': buscar_valor_xml(item, 'vBCFCP'), + 'aliquota_FCP': buscar_valor_xml(item, 'pFCP'), + 'valor_FCP': buscar_valor_xml(item, 'vFCP'), + 'modalidade_BC_ST': buscar_valor_xml(item, 'modBCST'), + 'aliquota_MVA_ST': buscar_valor_xml(item, 'pMVAST'), + 'valor_BC_ST': buscar_valor_xml(item, 'vBCST'), + 'aliquota_ICMS_ST': buscar_valor_xml(item, 'pICMSST'), + 'valor_ICMSST': buscar_valor_xml(item, 'vICMSST'), + 'valor_BC_FCPST': buscar_valor_xml(item, 'vBCFCPST'), + 'aliquota_FCPST': buscar_valor_xml(item, 'pFCPST'), + 'valor_FCPST': buscar_valor_xml(item, 'vFCPST'), + 'valor_II': buscar_valor_xml(item, 'vII'), + 'valor_IPI': buscar_valor_xml(item, 'vIPI'), + 'valor_ICMS_desonerado': buscar_valor_xml(item, 'vICMSDeson') + } + + lista_itens_notas.append(item_data) + + chaves_processadas += 1 + + else: + print(f" Erro na requisição") + chaves_com_erro += 1 + + except Exception as e: + print(f" Erro: {e}") + chaves_com_erro += 1 + + print(f"\n Chaves processadas: {chaves_processadas:,}") + print(f" Chaves com erro: {chaves_com_erro:,}") + print(f" Total de itens extraídos: {len(lista_itens_notas):,}") + + # Inserir itens no banco + if lista_itens_notas: + print(f"\n Inserindo {len(lista_itens_notas):,} itens no banco...") + inseridos = inserir_itens_banco(lista_itens_notas) + print(f" {inseridos:,} itens inseridos com sucesso!") + return inseridos + + return 0 + +# ============================================================================= +# ENVIAR STATUS PARA API +# ============================================================================= + +def enviar_status_api(sucesso): + """Envia status para a API de monitoramento""" + try: + print("\n" + "=" * 70) + print(" ENVIANDO STATUS PARA API DE MONITORAMENTO") + print("=" * 70) + + url = "https://api.grupoginseng.com.br/api/status/4" + sao_paulo_offset = timedelta(hours=-3) + current_datetime = datetime.now(timezone(sao_paulo_offset)).strftime("%Y-%m-%d %H:%M:%S") + + status_code = "OK" if sucesso else "FAIL" + + payload = { + "STATUS": status_code, + "DATA": current_datetime + } + + headers = {"Content-Type": "application/json"} + + response = requests.put(url, json=payload, headers=headers) + + print(f" Status: {status_code}") + print(f" Hora: {current_datetime}") + print(f" Response: {response.status_code}") + + except Exception as e: + print(f" Erro ao enviar status: {e}") + +# ============================================================================= +# EXECUÇÃO PRINCIPAL +# ============================================================================= + +def main(): + print("\n" + "=" * 70) + print(" SCRIPT COMPLETO - NOTAS E ITENS") + print(" Data de execução:", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print("=" * 70) + + sucesso = True + + try: + # PARTE 1: Buscar notas e identificar chaves novas + chaves_novas = buscar_notas_e_identificar_novas() + + # PARTE 2: Buscar itens das notas novas + if chaves_novas: + itens_inseridos = buscar_itens_das_chaves(chaves_novas) + else: + print("\n Nenhuma chave nova encontrada. Pulando busca de itens.") + itens_inseridos = 0 + + # Resumo final + print("\n" + "=" * 70) + print(" RESUMO FINAL") + print("=" * 70) + print(f" Notas novas encontradas: {len(chaves_novas):,}") + print(f" Itens inseridos: {itens_inseridos:,}") + + except Exception as e: + print(f"\n ERRO GERAL: {e}") + sucesso = False + + # Enviar status + enviar_status_api(sucesso) + + print("\n" + "=" * 70) + print(" PROCESSAMENTO CONCLUÍDO!") + print("=" * 70) + +if __name__ == "__main__": + main()