G-Scripts/extranet_notas_debito.py
daniel.rodrigues fbf60fed57 att
2025-10-31 11:26:49 -03:00

275 lines
10 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, 26)
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()