att
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import json
|
||||
from typing import Any, Dict, Optional, Set
|
||||
|
||||
import trf
|
||||
|
||||
FRANCHISES_LIST_URL = "https://sf-relatorios-api.grupoboticario.digital/v1/franchises/list/franchise"
|
||||
|
||||
|
||||
def _collect_store_codes(payload: Any) -> list[str]:
|
||||
keys = ("sapCode", "code", "franchiseId", "franchiseCode", "mediatorCode", "storeCode")
|
||||
out: list[str] = []
|
||||
seen = set()
|
||||
|
||||
def _push(v: Any) -> None:
|
||||
if v is None:
|
||||
return
|
||||
s = str(v).strip()
|
||||
if not s:
|
||||
return
|
||||
if s not in seen:
|
||||
seen.add(s)
|
||||
out.append(s)
|
||||
|
||||
def _walk(obj: Any) -> None:
|
||||
if isinstance(obj, dict):
|
||||
for k in keys:
|
||||
if k in obj:
|
||||
_push(obj.get(k))
|
||||
for v in obj.values():
|
||||
_walk(v)
|
||||
return
|
||||
if isinstance(obj, list):
|
||||
for item in obj:
|
||||
_walk(item)
|
||||
|
||||
_walk(payload)
|
||||
return out
|
||||
|
||||
|
||||
class ClientLoja(trf.Client):
|
||||
def __init__(self, store_code: str):
|
||||
super().__init__()
|
||||
self.store_code = str(store_code).strip()
|
||||
|
||||
def get_franchises(self, only_channels: Optional[Set[str]] = None):
|
||||
r = self.s.get(FRANCHISES_LIST_URL, headers=self._headers_json(), timeout=30)
|
||||
if r.status_code in (401, 403):
|
||||
self.auth.invalidate()
|
||||
r = self.s.get(FRANCHISES_LIST_URL, headers=self._headers_json(), timeout=30)
|
||||
r.raise_for_status()
|
||||
|
||||
body = r.json()
|
||||
source = body.get("data") if isinstance(body, dict) and "data" in body else body
|
||||
lojas = _collect_store_codes(source)
|
||||
if not lojas:
|
||||
raise RuntimeError(
|
||||
"Endpoint de franquias nao retornou codigos de loja validos. "
|
||||
f"Resposta resumida: {str(body)[:500]}"
|
||||
)
|
||||
|
||||
if self.store_code not in lojas:
|
||||
raise RuntimeError(
|
||||
f"Loja {self.store_code} nao encontrada/sem permissao no usuario. "
|
||||
f"Lojas disponiveis: {', '.join(lojas[:30])}{'...' if len(lojas) > 30 else ''}"
|
||||
)
|
||||
return [self.store_code]
|
||||
|
||||
|
||||
def sincronizar_paginas_sqlserver_loja(
|
||||
connection_string: str,
|
||||
store_code: str,
|
||||
cp_id: int = 10269,
|
||||
document_type: str = "EFAT",
|
||||
limit: int = 100,
|
||||
start_offset: int = 0,
|
||||
only_channels: Optional[Set[str]] = None,
|
||||
commit_cada_paginas: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
cli = ClientLoja(store_code)
|
||||
offset = start_offset
|
||||
paginas = 0
|
||||
docs_persistidos = 0
|
||||
total = None
|
||||
|
||||
with trf.SqlServerSink(connection_string) as sink:
|
||||
sink.ensure_schema()
|
||||
while True:
|
||||
pagina = cli.processar_pagina(
|
||||
cp_id=cp_id,
|
||||
document_type=document_type,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
only_channels=only_channels,
|
||||
)
|
||||
if total is None:
|
||||
total = int(pagina.get("total") or 0)
|
||||
|
||||
itens = pagina.get("items") or []
|
||||
persistidos_pag, novos_pag = sink.persist_items(itens)
|
||||
docs_persistidos += persistidos_pag
|
||||
paginas += 1
|
||||
|
||||
if paginas % commit_cada_paginas == 0:
|
||||
sink.cn.commit()
|
||||
|
||||
print(
|
||||
f"[sync][loja={store_code}] offset={offset} count={len(itens)} "
|
||||
f"novos_pag={novos_pag} persistidos={docs_persistidos} total={total}"
|
||||
)
|
||||
if not pagina.get("hasNext"):
|
||||
break
|
||||
offset += limit
|
||||
|
||||
sink.cn.commit()
|
||||
|
||||
return {
|
||||
"store_code": store_code,
|
||||
"total": total,
|
||||
"paginas_processadas": paginas,
|
||||
"documentos_persistidos": docs_persistidos,
|
||||
"offset_final": offset,
|
||||
}
|
||||
|
||||
|
||||
def sincronizar_incremental_sqlserver_loja(
|
||||
connection_string: str,
|
||||
store_code: str,
|
||||
cp_id: int = 10269,
|
||||
document_type: str = "EFAT",
|
||||
limit: int = 100,
|
||||
only_channels: Optional[Set[str]] = None,
|
||||
max_paginas_sem_novidade: int = 3,
|
||||
max_paginas: int = 20,
|
||||
) -> Dict[str, Any]:
|
||||
cli = ClientLoja(store_code)
|
||||
offset = 0
|
||||
paginas = 0
|
||||
docs_persistidos = 0
|
||||
docs_novos = 0
|
||||
sem_novidade = 0
|
||||
total = None
|
||||
|
||||
with trf.SqlServerSink(connection_string) as sink:
|
||||
sink.ensure_schema()
|
||||
while True:
|
||||
pagina = cli.processar_pagina(
|
||||
cp_id=cp_id,
|
||||
document_type=document_type,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
only_channels=only_channels,
|
||||
)
|
||||
if total is None:
|
||||
total = int(pagina.get("total") or 0)
|
||||
|
||||
itens = pagina.get("items") or []
|
||||
persistidos_pag, novos_pag = sink.persist_items(itens)
|
||||
sink.cn.commit()
|
||||
|
||||
docs_persistidos += persistidos_pag
|
||||
docs_novos += novos_pag
|
||||
paginas += 1
|
||||
sem_novidade = 0 if novos_pag > 0 else (sem_novidade + 1)
|
||||
|
||||
print(
|
||||
f"[inc][loja={store_code}] offset={offset} count={len(itens)} "
|
||||
f"novos_pag={novos_pag} sem_novidade={sem_novidade}/{max_paginas_sem_novidade} "
|
||||
f"total_novos={docs_novos}"
|
||||
)
|
||||
|
||||
if sem_novidade >= max_paginas_sem_novidade:
|
||||
break
|
||||
if paginas >= max_paginas:
|
||||
break
|
||||
if not pagina.get("hasNext"):
|
||||
break
|
||||
offset += limit
|
||||
|
||||
return {
|
||||
"store_code": store_code,
|
||||
"total": total,
|
||||
"paginas_processadas": paginas,
|
||||
"documentos_persistidos": docs_persistidos,
|
||||
"documentos_novos": docs_novos,
|
||||
"offset_final": offset,
|
||||
"parada_por_sem_novidade": sem_novidade >= max_paginas_sem_novidade,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
RUN_MODE = "full" # edite: full | incremental | json
|
||||
if RUN_MODE not in ("full", "incremental", "json"):
|
||||
raise RuntimeError("RUN_MODE invalido. Use: full, incremental ou json.")
|
||||
|
||||
TARGET_STORE_CODE = "24430" # edite: codigo da loja, ex: 20997
|
||||
store_code = str(TARGET_STORE_CODE).strip()
|
||||
if not store_code:
|
||||
raise RuntimeError("Preencha TARGET_STORE_CODE no codigo.")
|
||||
print(f"[info] consulta focada na loja {store_code}")
|
||||
|
||||
if RUN_MODE in ("full", "incremental"):
|
||||
SQLSERVER_CONN = (
|
||||
"DRIVER={ODBC Driver 17 for SQL Server};"
|
||||
"SERVER=10.77.77.10;"
|
||||
"DATABASE=GINSENG;"
|
||||
"UID=andrey;"
|
||||
"PWD=88253332;"
|
||||
"TrustServerCertificate=yes;"
|
||||
)
|
||||
|
||||
if RUN_MODE == "full":
|
||||
resultado = sincronizar_paginas_sqlserver_loja(
|
||||
connection_string=SQLSERVER_CONN,
|
||||
store_code=store_code,
|
||||
cp_id=10269,
|
||||
document_type="EFAT",
|
||||
limit=100,
|
||||
start_offset=0,
|
||||
only_channels=None,
|
||||
commit_cada_paginas=1,
|
||||
)
|
||||
else:
|
||||
resultado = sincronizar_incremental_sqlserver_loja(
|
||||
connection_string=SQLSERVER_CONN,
|
||||
store_code=store_code,
|
||||
cp_id=10269,
|
||||
document_type="EFAT",
|
||||
limit=100,
|
||||
only_channels=None,
|
||||
max_paginas_sem_novidade=5,
|
||||
max_paginas=15,
|
||||
)
|
||||
else:
|
||||
c = ClientLoja(store_code)
|
||||
resultado = c.processar_pagina(
|
||||
cp_id=10269,
|
||||
document_type="EFAT",
|
||||
offset=0,
|
||||
limit=25,
|
||||
only_channels=None,
|
||||
)
|
||||
|
||||
print(json.dumps(resultado, ensure_ascii=False, indent=2))
|
||||
|
||||
Reference in New Issue
Block a user