186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
"""
|
|
Consulta a API de parcelas por installmentGroupCode (orderNumber) e grava
|
|
em DocPedidos + DocPedidosParcelas.
|
|
|
|
Fonte dos códigos: SELECT orderNumber FROM Grgb_fiscal_invoices_items
|
|
"""
|
|
import os
|
|
import time
|
|
import requests
|
|
|
|
import installments_reader
|
|
from installments_reader import Auth, get_installments_page, upsert_doc_pedidos_sqlserver
|
|
|
|
# ─── CONFIGURE AQUI ───────────────────────────────────────────────────────────
|
|
WRITE_SQL = True # True = grava no banco | False = só lê e exibe
|
|
SKIP_EXISTING = True # True = pula códigos já gravados em DocPedidos
|
|
BATCH_SIZE = 10 # grava no banco a cada N pedidos processados
|
|
MIN_INTERVAL_MS = 3500 # ms mínimo entre chamadas à API
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
SQL_CONN = (
|
|
"DRIVER={ODBC Driver 17 for SQL Server};"
|
|
"SERVER=10.77.77.10;"
|
|
"DATABASE=GINSENG;"
|
|
"UID=andrey;"
|
|
"PWD=88253332;"
|
|
"TrustServerCertificate=yes;"
|
|
)
|
|
|
|
|
|
def _get_order_numbers() -> list[str]:
|
|
import pyodbc
|
|
cn = pyodbc.connect(SQL_CONN, timeout=30)
|
|
cur = cn.cursor()
|
|
cur.execute(
|
|
"SELECT DISTINCT [orderNumber] "
|
|
"FROM [GINSENG].[dbo].[Grgb_fiscal_invoices_items] "
|
|
"WHERE [orderNumber] IS NOT NULL AND LTRIM(RTRIM(CAST([orderNumber] AS VARCHAR))) <> ''"
|
|
)
|
|
return [str(row[0]).strip() for row in cur.fetchall()]
|
|
|
|
|
|
def _get_existing_group_codes() -> set[str]:
|
|
import pyodbc
|
|
try:
|
|
cn = pyodbc.connect(SQL_CONN, timeout=30)
|
|
cur = cn.cursor()
|
|
cur.execute("SELECT InstallmentGroupCode FROM dbo.DocPedidos")
|
|
return {str(row[0]).strip() for row in cur.fetchall()}
|
|
except Exception:
|
|
return set()
|
|
|
|
|
|
def _fetch_all_pages(session: requests.Session, auth: Auth, group_code: str) -> list:
|
|
all_items = []
|
|
page = 1
|
|
while True:
|
|
body = get_installments_page(
|
|
session=session,
|
|
auth=auth,
|
|
start_date=None,
|
|
end_date=None,
|
|
installment_change=None,
|
|
mediator_code=None,
|
|
page=page,
|
|
installment_group_code=group_code,
|
|
cookie_header=None,
|
|
)
|
|
items = ((body.get("data") or {}).get("installments")) or []
|
|
all_items.extend(items)
|
|
|
|
pagination = (body.get("data") or {}).get("pagination") or {}
|
|
total = int(pagination.get("total") or 0)
|
|
limit = int(pagination.get("limit") or len(items) or 10)
|
|
total_pages = max(1, (total + limit - 1) // limit) if total else page
|
|
|
|
print(f" pagina={page}/{total_pages} itens={len(items)}")
|
|
if not items or page >= total_pages:
|
|
break
|
|
page += 1
|
|
|
|
return all_items
|
|
|
|
|
|
def _flush(batch: list, label: str = "") -> dict:
|
|
if not batch or not WRITE_SQL:
|
|
return {}
|
|
stats = upsert_doc_pedidos_sqlserver(batch, SQL_CONN)
|
|
print(
|
|
f"[sql] {label} "
|
|
f"pedidos={stats.get('pedidos', 0)} parcelas={stats.get('parcelas', 0)}"
|
|
)
|
|
return stats
|
|
|
|
|
|
def main() -> None:
|
|
os.environ.setdefault("INSTALLMENTS_MIN_INTERVAL_MS", str(MIN_INTERVAL_MS))
|
|
os.environ.setdefault("INSTALLMENTS_429_MIN_WAIT_SEC", "45")
|
|
os.environ.setdefault("THROTTLE_RECOVERY_PAUSE_SEC", "900")
|
|
|
|
print("[info] buscando orderNumbers no banco...")
|
|
order_numbers = _get_order_numbers()
|
|
print(f"[info] total encontrado: {len(order_numbers)}")
|
|
|
|
existing: set[str] = set()
|
|
if SKIP_EXISTING:
|
|
existing = _get_existing_group_codes()
|
|
print(f"[info] já em DocPedidos: {len(existing)} — serão pulados")
|
|
|
|
pending = [c for c in order_numbers if c not in existing]
|
|
print(f"[info] para consultar na API: {len(pending)}\n")
|
|
|
|
session = requests.Session()
|
|
session.trust_env = False
|
|
auth = Auth(session)
|
|
|
|
batch: list = []
|
|
batch_first_idx: int = 0
|
|
batch_first_code: str = ""
|
|
total_pedidos = 0
|
|
total_parcelas = 0
|
|
erros = 0
|
|
start_ts = time.monotonic()
|
|
|
|
def _fmt(s: float) -> str:
|
|
s = int(s)
|
|
if s < 60:
|
|
return f"{s}s"
|
|
if s < 3600:
|
|
return f"{s // 60}m:{s % 60:02d}s"
|
|
return f"{s // 3600}h:{(s % 3600) // 60:02d}m"
|
|
|
|
for idx, group_code in enumerate(pending, 1):
|
|
elapsed = time.monotonic() - start_ts
|
|
avg_s = elapsed / idx
|
|
restante = avg_s * (len(pending) - idx)
|
|
print(f"[{idx}/{len(pending)}] group_code={group_code} "
|
|
f"decorrido={_fmt(elapsed)} restante~{_fmt(restante)}")
|
|
try:
|
|
items = _fetch_all_pages(session, auth, group_code)
|
|
if not items:
|
|
print(" sem dados na API")
|
|
continue
|
|
|
|
if not batch:
|
|
batch_first_idx = idx
|
|
batch_first_code = group_code
|
|
|
|
batch.append({
|
|
"installmentGroupCode": group_code,
|
|
"rawResponse": {"data": {"installments": items}},
|
|
"installments": items,
|
|
})
|
|
|
|
if len(batch) >= BATCH_SIZE:
|
|
label = (f"inserindo consultas {batch_first_idx}..{idx} "
|
|
f"(group {batch_first_code} até {group_code})")
|
|
stats = _flush(batch, label)
|
|
total_pedidos += stats.get("pedidos", 0)
|
|
total_parcelas += stats.get("parcelas", 0)
|
|
batch = []
|
|
batch_first_idx = 0
|
|
batch_first_code = ""
|
|
|
|
except Exception as exc:
|
|
print(f" [erro] {exc}")
|
|
erros += 1
|
|
|
|
# flush final
|
|
if batch:
|
|
label = (f"inserindo consultas {batch_first_idx}..{len(pending)} "
|
|
f"(group {batch_first_code} até {batch[-1]['installmentGroupCode']})")
|
|
else:
|
|
label = "flush final"
|
|
stats = _flush(batch, label)
|
|
total_pedidos += stats.get("pedidos", 0)
|
|
total_parcelas += stats.get("parcelas", 0)
|
|
|
|
print(
|
|
f"\n[fim] pedidos={total_pedidos} parcelas={total_parcelas} erros={erros}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|