""" Cliente da API do Tiny (Olist) — v3, OAuth2 client credentials. ATENÇÃO Wagner: confirmar na documentação oficial antes de subir: - o endpoint exato de marcadores do pedido - o limite de requisições por minuto (dimensiona o worker) - se o refresh token expira e com que frequência Este módulo isola a API: se o endpoint mudar, muda só aqui. """ from __future__ import annotations import os import time import httpx BASE = os.environ.get("TINY_BASE", "https://api.tiny.com.br/public-api/v3") CLIENT_ID = os.environ["TINY_CLIENT_ID"] CLIENT_SECRET = os.environ["TINY_CLIENT_SECRET"] TOKEN_URL = os.environ["TINY_TOKEN_URL"] _token: dict = {"valor": None, "expira": 0.0} def _acesso() -> str: if _token["valor"] and time.time() < _token["expira"] - 60: return _token["valor"] r = httpx.post(TOKEN_URL, data={ "grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, }, timeout=20) r.raise_for_status() d = r.json() _token["valor"] = d["access_token"] _token["expira"] = time.time() + d.get("expires_in", 3600) return _token["valor"] def _headers() -> dict: return {"Authorization": f"Bearer {_acesso()}", "Content-Type": "application/json"} def buscar_pedido(numero: str) -> dict: r = httpx.get(f"{BASE}/pedidos", params={"numero": numero}, headers=_headers(), timeout=20) r.raise_for_status() itens = r.json().get("itens") or [] if not itens: raise LookupError(f"pedido {numero} não encontrado no Tiny") return itens[0] def transferido_para_deposito(sku: str, deposito: str, dias: int) -> float: """ Soma o que foi transferido para o depósito de impressão no período. A Altus já faz essa transferência hoje, porque também revende insumo — o depósito separa consumo interno de revenda. Por isso o aproveitamento sai de graça: nenhum apontamento novo na sala. ATENÇÃO Wagner: confirmar o endpoint de movimentações de estoque por depósito na API v3 e o nome exato do depósito no cadastro. """ r = httpx.get( f"{BASE}/estoque/movimentacoes", params={"codigo": sku, "deposito": deposito, "dias": dias, "tipo": "E"}, headers=_headers(), timeout=30, ) r.raise_for_status() return sum(float(m.get("quantidade", 0)) for m in r.json().get("itens", [])) def marcador(numero: str, marcador: str) -> None: """ Troca o marcador do pedido. Substitui os marcadores de etapa do DTF, preservando marcadores de outra natureza (multiempresa, VALE, Troca...). """ pedido = buscar_pedido(numero) atuais = [m["descricao"] for m in pedido.get("marcadores", [])] # só três marcadores de etapa vivem no Tiny; o resto do fluxo é do kanban ETAPAS = {"DTF-RECEBIDA", "DTF-PRODUCAO", "CORRECAO DTF", "DTF-PRONTO"} mantem = [m for m in atuais if m not in ETAPAS] novos = mantem + [marcador] r = httpx.put(f"{BASE}/pedidos/{pedido['id']}/marcadores", json={"marcadores": [{"descricao": m} for m in novos]}, headers=_headers(), timeout=20) r.raise_for_status()