import os import sys import requests import pyodbc import unicodedata from datetime import datetime, timedelta # Detectar sistema operacional IS_WINDOWS = sys.platform.startswith('win') # Caminho baseado no sistema operacional if IS_WINDOWS: # Windows - caminho UNC direto OUTPUT_DIR_BASE = r"\\10.77.77.11\Contabilidade\AUTOMAÇÃO\NotadeDebito" else: # Linux/Kubernetes - volume montado pelo PV OUTPUT_DIR_BASE = "/mnt/contabilidade/AUTOMAÇÃO/NotadeDebito" DB_CONN = ( "Driver={ODBC Driver 17 for SQL Server};" "Server=10.77.77.10;" "Database=GINSENG;" "UID=andrey;" "PWD=88253332Wa@;" ) # 🔧 Normalizar caminho (acentuação e encoding) def normalize_path(path): """Normaliza acentuação e maiúsculas/minúsculas no caminho""" return unicodedata.normalize("NFC", path) # 🔧 Verificar acesso ao compartilhamento def check_storage_access(): """Verifica se o diretório de armazenamento está acessível""" print("📁 Verificando acesso ao armazenamento...") # Normalizar o caminho base_dir = normalize_path(OUTPUT_DIR_BASE.rsplit(os.sep, 1)[0]) out_dir = normalize_path(OUTPUT_DIR_BASE) try: # Criar diretório com permissões explícitas os.makedirs(out_dir, mode=0o777, exist_ok=True) print(f"✅ Diretório de saída pronto: {out_dir}") # Verificar permissões de escrita test_file = os.path.join(out_dir, ".test_write") try: with open(test_file, "w") as f: f.write("test") os.remove(test_file) print(f"✅ Permissões de escrita verificadas") except Exception as e: print(f"⚠️ Aviso: Problema ao testar escrita: {e}") # Mostrar informações do diretório if os.path.exists(out_dir): stat_info = os.stat(out_dir) print(f" 📊 Permissões: {oct(stat_info.st_mode)[-3:]}") print(f" 👤 UID: {stat_info.st_uid}, GID: {stat_info.st_gid}") return True except Exception as e: print(f"❌ Erro ao criar diretório {out_dir}: {e}") if not IS_WINDOWS: print("⚠️ ATENÇÃO: Verifique permissões no /mnt/contabilidade") import traceback traceback.print_exc() return False # 1️⃣ Token def get_token(): print("🔐 Obtendo token...") r = requests.get("https://api.grupoginseng.com.br/api/tokens") r.raise_for_status() token = r.json()["data"][0]["token"] print("✅ Token obtido com sucesso.") return token # 2️⃣ Última data sincronizada def get_last_invoice_date(cursor): cursor.execute("SELECT MAX(invoiceDate) FROM dbo.DebitNotes WHERE invoiceDate IS NOT NULL") result = cursor.fetchone()[0] if result: print(f"🕓 Última data sincronizada: {result}") return result else: default = datetime.now() - timedelta(days=5) print(f"⚙️ Nenhuma data encontrada, buscando últimos 30 dias ({default.date()})") return default # 3️⃣ Busca com paginação def get_all_documents(token): print("📡 Consultando notas fiscais (paginado)...") url_base = "https://sf-fiscal-api.grupoboticario.digital/v1/debit-notes/documents-list" headers = { "accept": "application/json, text/plain, */*", "authorization": token, "content-type": "application/json", } payload = { "franchiseId": [ "4494", "12522", "12817", "12818", "12820", "12823", "12824", "12826", "12828", "12829", "12830", "12838", "13427", "14617", "14668", "19103", "20005", "20006", "20009", "20056", "20057", "20441", "20683", "20712", "20858", "20968", "20969", "20970", "20979", "20986", "20988", "20989", "20991", "20992", "20993", "20994", "20995", "20996", "20997", "20998", "20999", "21000", "21001", "21007", "21068", "21277", "21278", "21296", "21375", "21381", "21383", "21495", "21624", "21647", "21737", "21738", "21739", "21740", "22448", "22475", "22501", "22526", "22532", "22533", "22541", "22593", "22632", "23156", "23475", "23665", "23701", "23702", "23703", "23704", "23705", "23706", "23707", "23708", "23709", "23711", "23712", "23713", "3546", "4560", "5699", "910173", "910291", "911486", "911487", "911488", "911489", "911490", "911491", "911492", "911509", "911510", "911511", "911512", "911513", "911514", "911515", "911516", "911517", "911518", "911519", "911762", "911766", "911924", "911940", "912242", "912273", "912856", "912857", "912858", "912859" ] } skip, take = 0, 2000 all_docs = [] while True: url = f"{url_base}?cpId=10269&skip={skip}&take={take}" print(f"➡️ Página {skip // take + 1} — buscando registros {skip}–{skip + take}...") r = requests.post(url, headers=headers, json=payload) if r.status_code != 200: print(f"❌ Erro {r.status_code}: {r.text}") break docs = r.json().get("documentsList", []) if not docs: print("✅ Nenhum novo documento encontrado.") break all_docs.extend(docs) print(f"📦 Recebidos {len(docs)} registros (total: {len(all_docs)})") if len(docs) < take: break skip += take return all_docs # 4️⃣ Filtro local por data def filter_documents_by_date(documents, start_date, end_date): filtrados = [] for doc in documents: try: inv_date = datetime.strptime(doc.get("invoiceDate", ""), "%Y-%m-%d") if start_date.date() <= inv_date.date() <= end_date.date(): filtrados.append(doc) except Exception: continue print(f"📅 {len(filtrados)} documentos dentro do intervalo {start_date}–{end_date}") return filtrados # 5️⃣ Inserção e download def record_exists(cursor, doc_id): cursor.execute("SELECT 1 FROM dbo.DebitNotes WHERE id = ?", doc_id) return cursor.fetchone() is not None def insert_record(cursor, doc): cursor.execute(""" INSERT INTO dbo.DebitNotes ( id, debitNoteOrigin, cnpj, cpId, franchiseId, debitNoteNumber, invoiceNumber, invoiceItem, invoiceDate, invoiceDueDate, value, serviceDescription, imageId, notificationRead, notificationReadDate, acknowledged, acknowledgedDate, UUID, imageName, uploadedFile ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( doc.get("id"), doc.get("debitNoteOrigin") or "N/D", doc.get("cnpj") or "", doc.get("cpId"), doc.get("franchiseId"), doc.get("debitNoteNumber") or "", doc.get("invoiceNumber") or "", doc.get("invoiceItem") or "", doc.get("invoiceDate"), doc.get("invoiceDueDate"), doc.get("value") or 0, doc.get("serviceDescription") or "", doc.get("imageId"), 1 if doc.get("notificationRead") else 0, doc.get("notificationReadDate"), 1 if doc.get("acknowledged") else 0, doc.get("acknowledgedDate"), doc.get("UUID"), doc.get("imageName"), 1 if doc.get("uploadedFile") else 0 )) def download_pdf(token, franchise_id, doc_id, image_name, invoice_date): try: print(f"📥 Baixando PDF: {image_name} (Data: {invoice_date})") # Obter URL do S3 url = f"https://sf-fiscal-api.grupoboticario.digital/v1/handle-images/NDEB/{franchise_id}/{doc_id}/{image_name}/download" r = requests.get(url, headers={"authorization": token}) r.raise_for_status() s3_url = r.text.strip() print(f" 🔗 URL S3 obtida: {s3_url[:50]}...") # Normalizar caminho da pasta pasta = normalize_path(os.path.join(OUTPUT_DIR_BASE, str(invoice_date))) print(f" 📁 Criando pasta: {pasta}") # Criar pasta com permissões explícitas os.makedirs(pasta, mode=0o777, exist_ok=True) # Verificar se a pasta foi criada if not os.path.exists(pasta): raise Exception(f"Pasta não foi criada: {pasta}") # Normalizar caminho do arquivo file_path = normalize_path(os.path.join(pasta, image_name)) print(f" 💾 Salvando em: {file_path}") # Baixar PDF pdf = requests.get(s3_url, stream=True) if pdf.status_code == 200: with open(file_path, "wb") as f: for chunk in pdf.iter_content(8192): if chunk: f.write(chunk) # Verificar se o arquivo foi salvo if os.path.exists(file_path): file_size = os.path.getsize(file_path) print(f" ✅ PDF salvo com sucesso: {file_path} ({file_size} bytes)") else: print(f" ⚠️ Arquivo não encontrado após salvar: {file_path}") else: print(f" ❌ Erro ao baixar PDF do S3: Status {pdf.status_code}") except Exception as e: print(f"❌ Erro ao baixar {image_name}: {e}") import traceback traceback.print_exc() # 🚀 Main def main(): # Verificar acesso ao armazenamento if not check_storage_access(): print("❌ Não foi possível acessar o armazenamento. Abortando.") return try: token = get_token() conn = pyodbc.connect(DB_CONN) cursor = conn.cursor() # Filtrar apenas notas do dia 27-10-2025 data_especifica = datetime(2025, 10, 27) todos = get_all_documents(token) documentos = filter_documents_by_date(todos, data_especifica, data_especifica) novos, ignorados = 0, 0 for doc in documentos: if record_exists(cursor, doc["id"]): ignorados += 1 continue insert_record(cursor, doc) novos += 1 download_pdf(token, doc["franchiseId"], doc["id"], doc["imageName"], doc["invoiceDate"]) conn.commit() conn.close() print(f"\n✅ Finalizado. {novos} novos registros, {ignorados} já existiam.") print(f"🕓 Execução concluída em {datetime.now().strftime('%H:%M:%S')}") except Exception as e: print(f"❌ Erro durante execução: {e}") import traceback traceback.print_exc() if __name__ == "__main__": main()