This commit is contained in:
Cunha 2026-05-25 17:41:51 -03:00
parent 94a7bc5e1b
commit 230654836b
4 changed files with 1330 additions and 0 deletions

152
enrich_paytype_backfill.py Normal file
View File

@ -0,0 +1,152 @@
"""
Backfill de paymentType para todos os Numero_Pedido em Grgb_vendas_report
que ainda não constam em Grgb_installment_paytype.
Pode ser interrompido e re-executado a qualquer momento retoma de onde parou.
"""
import re
import time
import requests
from typing import Optional
from installments_reader import Auth, get_installments_page
# ─── CONFIG ───────────────────────────────────────────────────────────────────
SQL_CONN = (
"DRIVER={ODBC Driver 17 for SQL Server};"
"SERVER=10.77.77.10;"
"DATABASE=GINSENG;"
"UID=andrey;"
"PWD=88253332;"
"TrustServerCertificate=yes;"
)
INSTALLMENT_GROUP_COL = "Numero_Pedido"
RATE_LIMIT_S = 1.5
PRINT_EVERY = 1
# ──────────────────────────────────────────────────────────────────────────────
def _conn_str() -> str:
s = SQL_CONN.rstrip(";")
if not re.search(r"(?i)\bEncrypt\b", s):
s += ";Encrypt=no"
return s + ";"
def _ensure_paytype_table(cur) -> None:
cur.execute("""
IF OBJECT_ID('dbo.Grgb_installment_paytype', 'U') IS NULL
BEGIN
CREATE TABLE dbo.Grgb_installment_paytype (
InstallmentGroupCode VARCHAR(50) NOT NULL PRIMARY KEY,
PaymentType VARCHAR(80) NULL,
ConsultadoEm DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
)
END
""")
def _load_pending(cur) -> list[str]:
cur.execute(f"""
SELECT DISTINCT [{INSTALLMENT_GROUP_COL}]
FROM dbo.Grgb_vendas_report
WHERE [{INSTALLMENT_GROUP_COL}] IS NOT NULL
AND [{INSTALLMENT_GROUP_COL}] <> ''
AND [{INSTALLMENT_GROUP_COL}] NOT IN (
SELECT InstallmentGroupCode FROM dbo.Grgb_installment_paytype
)
ORDER BY [{INSTALLMENT_GROUP_COL}]
""")
return [str(r[0]) for r in cur.fetchall()]
def _query_payment_type(
session: requests.Session,
auth: Auth,
group_code: str,
) -> Optional[str]:
"""Usa get_installments_page do installments_reader (headers + retry corretos)."""
try:
body = get_installments_page(
session=session,
auth=auth,
start_date=None,
end_date=None,
installment_change=None,
mediator_code=None,
page=1,
installment_group_code=group_code,
)
installments = body.get("data", {}).get("installments") or []
if installments:
return str(installments[0].get("paymentType") or "")
except Exception as exc:
print(f"[erro] groupCode={group_code}: {exc}")
return None
def _save(cur, group_code: str, payment_type: Optional[str]) -> None:
cur.execute(
"INSERT INTO dbo.Grgb_installment_paytype (InstallmentGroupCode, PaymentType) "
"VALUES (?, ?)",
group_code, payment_type,
)
def main() -> None:
import pyodbc
conn_str = _conn_str()
cn = pyodbc.connect(conn_str, timeout=60)
cn.autocommit = False
cur = cn.cursor()
_ensure_paytype_table(cur)
cn.commit()
pending = _load_pending(cur)
cur.close(); cn.close()
total = len(pending)
print(f"[backfill] {total} pedidos pendentes de paymentType")
if not total:
print("[backfill] nada a fazer.")
return
session = requests.Session()
session.trust_env = False
auth = Auth(session)
ok = erros = 0
for i, group_code in enumerate(pending, 1):
payment_type = _query_payment_type(session, auth, group_code)
cn = pyodbc.connect(conn_str, timeout=60)
cn.autocommit = False
cur = cn.cursor()
try:
_save(cur, group_code, payment_type)
cn.commit()
ok += 1
except Exception as exc:
cn.rollback()
print(f"[erro] sql groupCode={group_code}: {exc}")
erros += 1
finally:
cur.close(); cn.close()
if i % PRINT_EVERY == 0 or i == total:
pct = i / total * 100
print(
f"[backfill] {i}/{total} ({pct:.1f}%)"
f" ok={ok} erros={erros}"
f" ultimo={group_code} paymentType={payment_type}"
)
time.sleep(RATE_LIMIT_S)
print(f"\n[backfill] concluido: {ok} gravados, {erros} erros")
if __name__ == "__main__":
main()

View File

@ -57,6 +57,12 @@ WRITE_SQL = True
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
WATERMARK_FILE = os.path.join(_SCRIPT_DIR, "vendas_watermark.json") WATERMARK_FILE = os.path.join(_SCRIPT_DIR, "vendas_watermark.json")
IMPORT_BATCH_SIZE = 500 # linhas por lote no SQL Server IMPORT_BATCH_SIZE = 500 # linhas por lote no SQL Server
ENRICH_PAYMENT_TYPES = True # True = consulta API de parcelas para preencher paymentType
# Nome da coluna em Grgb_vendas_report que contém o installmentGroupCode.
# Verifique rodando: SELECT TOP 1 * FROM dbo.Grgb_vendas_report
INSTALLMENT_GROUP_COL = "Numero_Pedido"
ENRICH_RATE_LIMIT_S = 0.3 # pausa entre chamadas à API de parcelas
# ────────────────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────────────────
SQL_CONN = ( SQL_CONN = (
@ -72,6 +78,10 @@ REPORTS_URL = (
"https://bff-portal-apigw.produto-financeiro.grupoboticario.digital" "https://bff-portal-apigw.produto-financeiro.grupoboticario.digital"
"/v2/franchisee/reports" "/v2/franchisee/reports"
) )
INSTALLMENTS_URL = (
"https://bff-credit-container-portal-apigw.prd.produto-financeiro.app.grupoboticario.com.br"
"/v1/franchisee/installments"
)
_DONE_STATUSES = {"generated", "generated_csv", "done", "completed", "ready", "available"} _DONE_STATUSES = {"generated", "generated_csv", "done", "completed", "ready", "available"}
_FAIL_STATUSES = {"failed", "error", "erro", "falha"} _FAIL_STATUSES = {"failed", "error", "erro", "falha"}
@ -461,6 +471,125 @@ VALUES (?, ?, ?, ?)
return inserted return inserted
# ─── ENRIQUECIMENTO paymentType ───────────────────────────────────────────────
def _ensure_paytype_table(cur) -> None:
cur.execute("""
IF OBJECT_ID('dbo.Grgb_installment_paytype', 'U') IS NULL
BEGIN
CREATE TABLE dbo.Grgb_installment_paytype (
InstallmentGroupCode VARCHAR(50) NOT NULL PRIMARY KEY,
PaymentType VARCHAR(80) NULL,
ConsultadoEm DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
)
END
""")
def _fetch_pending_group_codes(cur, col_name: str, file_name: str) -> list[str]:
"""Retorna installmentGroupCodes do arquivo atual que ainda não têm paymentType."""
cur.execute(f"""
SELECT DISTINCT [{col_name}]
FROM dbo.Grgb_vendas_report
WHERE NomeArquivo = ?
AND [{col_name}] IS NOT NULL
AND [{col_name}] <> ''
AND [{col_name}] NOT IN (
SELECT InstallmentGroupCode FROM dbo.Grgb_installment_paytype
)
""", file_name)
return [str(r[0]) for r in cur.fetchall()]
def _query_payment_type(
session: requests.Session,
auth: Auth,
group_code: str,
) -> Optional[str]:
"""Consulta a API de parcelas e retorna o paymentType da primeira parcela."""
try:
r = session.get(
INSTALLMENTS_URL,
params={"installmentGroupCode": group_code, "page": 1},
headers=_headers(auth),
timeout=30,
)
if not r.ok:
print(f"[enrich] {r.status_code} para groupCode={group_code}")
return None
installments = r.json().get("data", {}).get("installments") or []
if installments:
return str(installments[0].get("paymentType") or "")
except Exception as exc:
print(f"[enrich] erro ao consultar groupCode={group_code}: {exc}")
return None
def enrich_payment_types(session: requests.Session, auth: Auth, file_name: str) -> None:
"""
Para cada installmentGroupCode do arquivo atual que ainda não consta
em Grgb_installment_paytype, consulta a API e persiste o paymentType.
"""
import pyodbc
conn_str = _sql_conn_str()
cn = pyodbc.connect(conn_str, timeout=60)
cn.autocommit = False
cur = cn.cursor()
_ensure_paytype_table(cur)
cn.commit()
# verifica se a coluna configurada existe na tabela de dados
cur.execute("""
SELECT LOWER(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='dbo' AND TABLE_NAME='Grgb_vendas_report'
""")
existing_cols = {r[0] for r in cur.fetchall()}
col_name = INSTALLMENT_GROUP_COL
if col_name.lower() not in existing_cols:
print(
f"[enrich] coluna '{col_name}' nao encontrada em Grgb_vendas_report. "
f"Ajuste INSTALLMENT_GROUP_COL. Colunas disponiveis: {sorted(existing_cols)}"
)
cur.close(); cn.close()
return
pending = _fetch_pending_group_codes(cur, col_name, file_name)
cur.close(); cn.close()
total = len(pending)
print(f"[enrich] {total} installmentGroupCode(s) para consultar")
if not total:
return
for i, group_code in enumerate(pending, 1):
payment_type = _query_payment_type(session, auth, group_code)
cn = pyodbc.connect(conn_str, timeout=60)
cn.autocommit = False
cur = cn.cursor()
try:
cur.execute(
"INSERT INTO dbo.Grgb_installment_paytype (InstallmentGroupCode, PaymentType) "
"VALUES (?, ?)",
group_code, payment_type,
)
cn.commit()
except Exception:
cn.rollback()
raise
finally:
cur.close(); cn.close()
if i % 50 == 0 or i == total:
print(f"[enrich] {i}/{total} ultimo={group_code} paymentType={payment_type}")
time.sleep(ENRICH_RATE_LIMIT_S)
print(f"[enrich] concluido: {total} registros gravados em Grgb_installment_paytype")
# ─── MAIN ───────────────────────────────────────────────────────────────────── # ─── MAIN ─────────────────────────────────────────────────────────────────────
def _resolve_date_range() -> tuple[str, str]: def _resolve_date_range() -> tuple[str, str]:
@ -544,6 +673,9 @@ def main() -> None:
else: else:
n = _import_csv(rows, csv_cols, file_name, chunk_start, chunk_end) n = _import_csv(rows, csv_cols, file_name, chunk_start, chunk_end)
total_linhas += n total_linhas += n
if ENRICH_PAYMENT_TYPES and n > 0:
print(f"[enrich] enriquecendo paymentType para arquivo {file_name}...")
enrich_payment_types(session, auth, file_name)
if INCREMENTAL: if INCREMENTAL:
_save_watermark(chunk_end) _save_watermark(chunk_end)

1045
test_installment.py Normal file

File diff suppressed because it is too large Load Diff

1
vendas_watermark.json Normal file
View File

@ -0,0 +1 @@
{"last_end_date": "2026-05-24"}