first commit
This commit is contained in:
89
kanban/tiny.py
Normal file
89
kanban/tiny.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user