Update routes/dashboard_home.py
Browse files- routes/dashboard_home.py +76 -79
routes/dashboard_home.py
CHANGED
@@ -250,6 +250,82 @@ async def get_platform_users() -> Dict[str, Any]:
|
|
250 |
"error": str(e)
|
251 |
}
|
252 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
253 |
async def get_monthly_revenue_data(target_date) -> Dict[str, Any]:
|
254 |
"""Obtém o faturamento de um mês específico de forma otimizada"""
|
255 |
ny_timezone = pytz.timezone('America/New_York')
|
@@ -289,85 +365,6 @@ async def get_monthly_revenue_data(target_date) -> Dict[str, Any]:
|
|
289 |
"transfer_count": transfer_data["transfer_count"]
|
290 |
}
|
291 |
|
292 |
-
async def get_top_stylists(start_timestamp: Optional[int] = None, end_timestamp: Optional[int] = None, limit: int = 10) -> List[Dict[str, Any]]:
|
293 |
-
"""Obtém os estilistas que mais receberam pagamentos no período"""
|
294 |
-
try:
|
295 |
-
# Obter transferências no período
|
296 |
-
query_params = {"limit": 100}
|
297 |
-
|
298 |
-
# Adicionar filtros de data se fornecidos
|
299 |
-
if start_timestamp and end_timestamp:
|
300 |
-
query_params["created"] = {"gte": start_timestamp, "lte": end_timestamp}
|
301 |
-
elif start_timestamp:
|
302 |
-
query_params["created"] = {"gte": start_timestamp}
|
303 |
-
elif end_timestamp:
|
304 |
-
query_params["created"] = {"lte": end_timestamp}
|
305 |
-
|
306 |
-
transfers = []
|
307 |
-
has_more = True
|
308 |
-
last_id = None
|
309 |
-
|
310 |
-
# Usar batch para reduzir chamadas
|
311 |
-
while has_more:
|
312 |
-
if last_id:
|
313 |
-
query_params["starting_after"] = last_id
|
314 |
-
|
315 |
-
# Usar thread para não bloquear o evento loop
|
316 |
-
transfer_list = await fetch_stripe_data(stripe.Transfer.list, expand=["data.destination"], **query_params)
|
317 |
-
transfers.extend(transfer_list.data)
|
318 |
-
|
319 |
-
has_more = transfer_list.has_more
|
320 |
-
if transfer_list.data:
|
321 |
-
last_id = transfer_list.data[-1].id
|
322 |
-
else:
|
323 |
-
has_more = False
|
324 |
-
|
325 |
-
# Agrupar transferências por estilista (destination account)
|
326 |
-
stylist_totals = {}
|
327 |
-
for transfer in transfers:
|
328 |
-
destination = transfer.destination
|
329 |
-
if destination not in stylist_totals:
|
330 |
-
stylist_totals[destination] = {
|
331 |
-
"account_id": destination,
|
332 |
-
"total_received": 0
|
333 |
-
}
|
334 |
-
stylist_totals[destination]["total_received"] += transfer.amount
|
335 |
-
|
336 |
-
# Ordenar estilistas por valor recebido
|
337 |
-
top_stylists_by_account = sorted(
|
338 |
-
stylist_totals.values(),
|
339 |
-
key=lambda x: x["total_received"],
|
340 |
-
reverse=True
|
341 |
-
)[:limit]
|
342 |
-
|
343 |
-
# Buscar informações adicionais dos estilistas no Supabase
|
344 |
-
async with aiohttp.ClientSession() as session:
|
345 |
-
top_stylists = []
|
346 |
-
for stylist_data in top_stylists_by_account:
|
347 |
-
account_id = stylist_data["account_id"]
|
348 |
-
|
349 |
-
# Buscar usuário correspondente pelo Stripe account_id
|
350 |
-
query_url = f"{SUPABASE_URL}/rest/v1/User?stripe_account_id=eq.{account_id}&select=id,name,avatar"
|
351 |
-
async with session.get(query_url, headers=SUPABASE_HEADERS) as response:
|
352 |
-
if response.status == 200:
|
353 |
-
user_data = await response.json()
|
354 |
-
if user_data and len(user_data) > 0:
|
355 |
-
# Combinar dados do Stripe com dados do Supabase
|
356 |
-
stylist_info = {
|
357 |
-
"id": user_data[0].get("id"),
|
358 |
-
"name": user_data[0].get("name", "Estilista"),
|
359 |
-
"avatar": user_data[0].get("avatar"),
|
360 |
-
"total_received": stylist_data["total_received"],
|
361 |
-
"total_transfers": stylist_totals[account_id].get("total_transfers", 1)
|
362 |
-
}
|
363 |
-
top_stylists.append(stylist_info)
|
364 |
-
|
365 |
-
return top_stylists[:limit]
|
366 |
-
|
367 |
-
except Exception as e:
|
368 |
-
logger.error(f"❌ Erro ao obter top estilistas: {str(e)}")
|
369 |
-
return []
|
370 |
-
|
371 |
@router.get("/admin/dashboard")
|
372 |
async def get_admin_dashboard(
|
373 |
user_token: str = Header(None, alias="User-key"),
|
|
|
250 |
"error": str(e)
|
251 |
}
|
252 |
|
253 |
+
async def get_top_stylists(start_timestamp: Optional[int] = None, end_timestamp: Optional[int] = None, limit: int = 10) -> List[Dict[str, Any]]:
|
254 |
+
"""Obtém os estilistas com mais assinantes ativos de forma otimizada"""
|
255 |
+
try:
|
256 |
+
# Construir a query para obter assinaturas ativas
|
257 |
+
subscriptions_url = f"{SUPABASE_URL}/rest/v1/Subscriptions?active=eq.true&select=stylist_id"
|
258 |
+
|
259 |
+
# Adicionar filtros de data se fornecidos
|
260 |
+
if start_timestamp or end_timestamp:
|
261 |
+
date_filters = []
|
262 |
+
if start_timestamp:
|
263 |
+
start_date = datetime.fromtimestamp(start_timestamp).strftime("%Y-%m-%d")
|
264 |
+
date_filters.append(f"created_at=gte.{start_date}")
|
265 |
+
if end_timestamp:
|
266 |
+
end_date = datetime.fromtimestamp(end_timestamp).strftime("%Y-%m-%d")
|
267 |
+
date_filters.append(f"created_at=lte.{end_date}")
|
268 |
+
|
269 |
+
if date_filters:
|
270 |
+
date_query = "&".join(date_filters)
|
271 |
+
subscriptions_url = f"{subscriptions_url}&{date_query}"
|
272 |
+
|
273 |
+
# Fazer a requisição para as assinaturas
|
274 |
+
async with aiohttp.ClientSession() as session:
|
275 |
+
async with session.get(subscriptions_url, headers=SUPABASE_HEADERS) as response:
|
276 |
+
if response.status != 200:
|
277 |
+
logger.error(f"❌ Erro ao obter assinaturas: {response.status}")
|
278 |
+
return []
|
279 |
+
|
280 |
+
subscriptions_data = await response.json()
|
281 |
+
|
282 |
+
# Contar assinaturas por estilista
|
283 |
+
stylist_counts = {}
|
284 |
+
for subscription in subscriptions_data:
|
285 |
+
stylist_id = subscription.get("stylist_id")
|
286 |
+
if stylist_id:
|
287 |
+
stylist_counts[stylist_id] = stylist_counts.get(stylist_id, 0) + 1
|
288 |
+
|
289 |
+
# Obter IDs dos top estilistas
|
290 |
+
top_stylist_ids = sorted(stylist_counts.keys(), key=lambda x: stylist_counts[x], reverse=True)[:limit]
|
291 |
+
|
292 |
+
# Sem estilistas encontrados
|
293 |
+
if not top_stylist_ids:
|
294 |
+
return []
|
295 |
+
|
296 |
+
# Construir a query para obter detalhes dos estilistas
|
297 |
+
stylists_ids_str = ",".join([f"eq.{id}" for id in top_stylist_ids])
|
298 |
+
stylists_url = f"{SUPABASE_URL}/rest/v1/User?id=in.({','.join(top_stylist_ids)})&select=id,name,avatar"
|
299 |
+
|
300 |
+
# Obter detalhes dos estilistas
|
301 |
+
async with aiohttp.ClientSession() as session:
|
302 |
+
async with session.get(stylists_url, headers=SUPABASE_HEADERS) as response:
|
303 |
+
if response.status != 200:
|
304 |
+
logger.error(f"❌ Erro ao obter detalhes dos estilistas: {response.status}")
|
305 |
+
return []
|
306 |
+
|
307 |
+
stylists_data = await response.json()
|
308 |
+
|
309 |
+
# Combinar contagem com detalhes e ordenar por número de assinantes
|
310 |
+
result = []
|
311 |
+
for stylist in stylists_data:
|
312 |
+
stylist_id = stylist.get("id")
|
313 |
+
if stylist_id in stylist_counts:
|
314 |
+
result.append({
|
315 |
+
"id": stylist_id,
|
316 |
+
"name": stylist.get("name", "Nome não disponível"),
|
317 |
+
"avatar": stylist.get("avatar", ""),
|
318 |
+
"subscription_count": stylist_counts[stylist_id]
|
319 |
+
})
|
320 |
+
|
321 |
+
# Ordenar por número de assinantes (decrescente)
|
322 |
+
result.sort(key=lambda x: x["subscription_count"], reverse=True)
|
323 |
+
return result[:limit] # Limitar ao número solicitado
|
324 |
+
|
325 |
+
except Exception as e:
|
326 |
+
logger.error(f"❌ Erro ao obter top estilistas: {str(e)}")
|
327 |
+
return []
|
328 |
+
|
329 |
async def get_monthly_revenue_data(target_date) -> Dict[str, Any]:
|
330 |
"""Obtém o faturamento de um mês específico de forma otimizada"""
|
331 |
ny_timezone = pytz.timezone('America/New_York')
|
|
|
365 |
"transfer_count": transfer_data["transfer_count"]
|
366 |
}
|
367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
@router.get("/admin/dashboard")
|
369 |
async def get_admin_dashboard(
|
370 |
user_token: str = Header(None, alias="User-key"),
|