habulaj commited on
Commit
b2dda13
·
verified ·
1 Parent(s): 4e00705

Update routes/dashboard_home.py

Browse files
Files changed (1) hide show
  1. routes/dashboard_home.py +62 -221
routes/dashboard_home.py CHANGED
@@ -6,7 +6,7 @@ import pytz
6
  from fastapi import APIRouter, HTTPException, Header, Query
7
  from datetime import datetime, timedelta
8
  from dateutil.relativedelta import relativedelta
9
- from typing import List, Dict, Any, Optional
10
  from pydantic import BaseModel
11
 
12
  router = APIRouter()
@@ -53,7 +53,7 @@ def verify_admin_token(user_token: str) -> str:
53
  if not user_id:
54
  raise HTTPException(status_code=400, detail="ID do usuário não encontrado")
55
 
56
- # Agora verifica se o usuário é um administrador
57
  user_data_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
58
  response = requests.get(user_data_url, headers=SUPABASE_HEADERS)
59
 
@@ -71,16 +71,11 @@ def verify_admin_token(user_token: str) -> str:
71
  def get_total_platform_revenue(start_timestamp: int = None, end_timestamp: int = None) -> Dict[str, Any]:
72
  """Obtém o faturamento total da plataforma no Stripe"""
73
  try:
74
- query_params = {
75
- "limit": 100
76
- }
77
 
78
  # Adicionar filtros de data se fornecidos
79
  if start_timestamp and end_timestamp:
80
- query_params["created"] = {
81
- "gte": start_timestamp,
82
- "lte": end_timestamp
83
- }
84
  elif start_timestamp:
85
  query_params["created"] = {"gte": start_timestamp}
86
  elif end_timestamp:
@@ -95,7 +90,6 @@ def get_total_platform_revenue(start_timestamp: int = None, end_timestamp: int =
95
  if last_id:
96
  query_params["starting_after"] = last_id
97
 
98
- # Obter pagamentos (charges)
99
  payment_list = stripe.Charge.list(**query_params)
100
  payments.extend(payment_list.data)
101
 
@@ -112,11 +106,8 @@ def get_total_platform_revenue(start_timestamp: int = None, end_timestamp: int =
112
 
113
  for payment in payments:
114
  amount = payment.amount
115
-
116
- # Contar todas as tentativas para o valor bruto
117
  total_revenue += amount
118
 
119
- # Separar por status
120
  if payment.status == 'succeeded':
121
  successful_revenue += amount
122
  elif payment.status == 'failed':
@@ -144,16 +135,11 @@ def get_total_platform_revenue(start_timestamp: int = None, end_timestamp: int =
144
  def get_platform_transfers(start_timestamp: int = None, end_timestamp: int = None) -> Dict[str, Any]:
145
  """Obtém o total transferido para estilistas"""
146
  try:
147
- query_params = {
148
- "limit": 100
149
- }
150
 
151
  # Adicionar filtros de data se fornecidos
152
  if start_timestamp and end_timestamp:
153
- query_params["created"] = {
154
- "gte": start_timestamp,
155
- "lte": end_timestamp
156
- }
157
  elif start_timestamp:
158
  query_params["created"] = {"gte": start_timestamp}
159
  elif end_timestamp:
@@ -168,7 +154,6 @@ def get_platform_transfers(start_timestamp: int = None, end_timestamp: int = Non
168
  if last_id:
169
  query_params["starting_after"] = last_id
170
 
171
- # Obter transferências
172
  transfer_list = stripe.Transfer.list(**query_params)
173
  transfers.extend(transfer_list.data)
174
 
@@ -215,153 +200,45 @@ def get_app_revenue_share(total_revenue: int, total_transferred: int) -> Dict[st
215
  "currency": "BRL"
216
  }
217
 
218
- def get_monthly_revenue_breakdown(months: int = 12) -> List[Dict[str, Any]]:
219
- """Obtém o faturamento mensal nos últimos X meses"""
220
  ny_timezone = pytz.timezone('America/New_York')
221
- now_ny = datetime.now(ny_timezone)
222
- monthly_data = []
223
 
224
- for i in range(months):
225
- # Calcular início e fim do mês
226
- target_date = now_ny - relativedelta(months=i)
227
- month_start = datetime(target_date.year, target_date.month, 1, tzinfo=ny_timezone)
228
- if target_date.month == 12:
229
- month_end = datetime(target_date.year + 1, 1, 1, tzinfo=ny_timezone) - timedelta(seconds=1)
230
- else:
231
- month_end = datetime(target_date.year, target_date.month + 1, 1, tzinfo=ny_timezone) - timedelta(seconds=1)
232
-
233
- # Converter para timestamps
234
- start_timestamp = int(month_start.timestamp())
235
- end_timestamp = int(month_end.timestamp())
236
-
237
- # Obter dados do mês
238
- revenue_data = get_total_platform_revenue(start_timestamp, end_timestamp)
239
- transfer_data = get_platform_transfers(start_timestamp, end_timestamp)
240
-
241
- # Calcular divisão app/estilistas
242
- total_revenue = revenue_data["successful_revenue"]
243
- total_transferred = transfer_data["total_transferred_to_stylists"]
244
- app_revenue = total_revenue - total_transferred
245
-
246
- # Calcular percentual de crescimento (comparado ao mês anterior)
247
- growth_percentage = 0
248
- growth_status = "neutral"
249
-
250
- if i < months - 1 and len(monthly_data) > 0:
251
- prev_month = monthly_data[0]
252
- prev_revenue = prev_month["total_revenue"]
253
-
254
- if prev_revenue > 0:
255
- growth_percentage = ((total_revenue - prev_revenue) / prev_revenue) * 100
256
- growth_status = "up" if growth_percentage > 0 else "down" if growth_percentage < 0 else "neutral"
257
-
258
- monthly_data.insert(0, {
259
- "year": target_date.year,
260
- "month": target_date.month,
261
- "name": target_date.strftime("%b"),
262
- "total_revenue": total_revenue,
263
- "app_revenue": app_revenue,
264
- "stylists_revenue": total_transferred,
265
- "payment_count": revenue_data["payment_count"],
266
- "transfer_count": transfer_data["transfer_count"],
267
- "growth": {
268
- "percentage": round(growth_percentage, 2),
269
- "status": growth_status,
270
- "formatted": f"{round(growth_percentage, 2)}%"
271
- }
272
- })
273
 
274
- return monthly_data
275
-
276
- def get_top_earning_stylists(limit: int = 10, start_timestamp: int = None, end_timestamp: int = None) -> List[Dict[str, Any]]:
277
- """Obtém os estilistas com maior faturamento no período"""
278
- try:
279
- query_params = {
280
- "limit": 100,
281
- "expand": ["destination"]
282
- }
283
-
284
- # Adicionar filtros de data se fornecidos
285
- if start_timestamp and end_timestamp:
286
- query_params["created"] = {
287
- "gte": start_timestamp,
288
- "lte": end_timestamp
289
- }
290
- elif start_timestamp:
291
- query_params["created"] = {"gte": start_timestamp}
292
- elif end_timestamp:
293
- query_params["created"] = {"lte": end_timestamp}
294
-
295
- transfers = []
296
- has_more = True
297
- last_id = None
298
-
299
- # Paginar resultados para obter todas as transferências
300
- while has_more:
301
- if last_id:
302
- query_params["starting_after"] = last_id
303
-
304
- # Obter transferências
305
- transfer_list = stripe.Transfer.list(**query_params)
306
- transfers.extend(transfer_list.data)
307
-
308
- has_more = transfer_list.has_more
309
- if transfer_list.data:
310
- last_id = transfer_list.data[-1].id
311
- else:
312
- has_more = False
313
-
314
- # Agrupar transferências por conta de destino (estilista)
315
- stylist_earnings = {}
316
- for transfer in transfers:
317
- destination = transfer.destination
318
- if destination not in stylist_earnings:
319
- stylist_earnings[destination] = {
320
- "account_id": destination,
321
- "total_earnings": 0,
322
- "transfer_count": 0,
323
- "name": None
324
- }
325
-
326
- stylist_earnings[destination]["total_earnings"] += transfer.amount
327
- stylist_earnings[destination]["transfer_count"] += 1
328
-
329
- # Obter informações adicionais dos estilistas do Supabase
330
- stylist_accounts = list(stylist_earnings.keys())
331
-
332
- # Consultar informações de usuário para cada estilista
333
- for account_id in stylist_accounts:
334
- try:
335
- user_data_url = f"{SUPABASE_URL}/rest/v1/User?stripe_id=eq.{account_id}"
336
- response = requests.get(user_data_url, headers=SUPABASE_HEADERS)
337
-
338
- if response.status_code == 200 and response.json():
339
- user_info = response.json()[0]
340
- stylist_earnings[account_id]["name"] = user_info.get("name", "Unknown")
341
- stylist_earnings[account_id]["id"] = user_info.get("id")
342
- stylist_earnings[account_id]["avatar"] = user_info.get("avatar")
343
- else:
344
- # Tentar obter dados direto do Stripe como fallback
345
- account = stripe.Account.retrieve(account_id)
346
- stylist_earnings[account_id]["name"] = account.business_profile.name if hasattr(account, 'business_profile') else "Unknown"
347
- except Exception as e:
348
- logger.error(f"❌ Erro ao obter informações do estilista {account_id}: {str(e)}")
349
-
350
- # Ordenar por faturamento e limitar ao número solicitado
351
- top_stylists = sorted(
352
- stylist_earnings.values(),
353
- key=lambda x: x["total_earnings"],
354
- reverse=True
355
- )[:limit]
356
-
357
- return top_stylists
358
-
359
- except Exception as e:
360
- logger.error(f"❌ Erro ao obter top estilistas: {str(e)}")
361
- return []
362
 
363
- def get_platform_metrics() -> Dict[str, Any]:
364
- """Obtém métricas gerais da plataforma"""
365
  try:
366
  # Obter número total de usuários
367
  users_url = f"{SUPABASE_URL}/rest/v1/User?select=count"
@@ -377,7 +254,7 @@ def get_platform_metrics() -> Dict[str, Any]:
377
  total_users = int(count_header.split("/")[1])
378
 
379
  # Obter número total de estilistas
380
- stylists_url = f"{SUPABASE_URL}/rest/v1/User?is_stylist=eq.true&select=count"
381
  stylists_response = requests.get(
382
  stylists_url,
383
  headers={**SUPABASE_HEADERS, "Prefer": "count=exact"}
@@ -389,59 +266,23 @@ def get_platform_metrics() -> Dict[str, Any]:
389
  if count_header:
390
  total_stylists = int(count_header.split("/")[1])
391
 
392
- # Obter número total de assinaturas ativas
393
- subscriptions_url = f"{SUPABASE_URL}/rest/v1/Subscriptions?active=eq.true&select=count"
394
- subscriptions_response = requests.get(
395
- subscriptions_url,
396
- headers={**SUPABASE_HEADERS, "Prefer": "count=exact"}
397
- )
398
-
399
- total_active_subscriptions = 0
400
- if subscriptions_response.status_code == 200:
401
- count_header = subscriptions_response.headers.get("Content-Range", "")
402
- if count_header:
403
- total_active_subscriptions = int(count_header.split("/")[1])
404
-
405
- # Calcular taxa de conversão (assinaturas ativas / usuários totais)
406
- conversion_rate = (total_active_subscriptions / total_users) * 100 if total_users > 0 else 0
407
-
408
- # Obter número total de consultas
409
- schedules_url = f"{SUPABASE_URL}/rest/v1/schedules?select=count"
410
- schedules_response = requests.get(
411
- schedules_url,
412
- headers={**SUPABASE_HEADERS, "Prefer": "count=exact"}
413
- )
414
-
415
- total_consultations = 0
416
- if schedules_response.status_code == 200:
417
- count_header = schedules_response.headers.get("Content-Range", "")
418
- if count_header:
419
- total_consultations = int(count_header.split("/")[1])
420
-
421
  return {
422
  "total_users": total_users,
423
- "total_stylists": total_stylists,
424
- "total_active_subscriptions": total_active_subscriptions,
425
- "conversion_rate": round(conversion_rate, 2),
426
- "total_consultations": total_consultations
427
  }
428
 
429
  except Exception as e:
430
- logger.error(f"❌ Erro ao obter métricas da plataforma: {str(e)}")
431
  return {
432
  "total_users": 0,
433
  "total_stylists": 0,
434
- "total_active_subscriptions": 0,
435
- "conversion_rate": 0,
436
- "total_consultations": 0,
437
  "error": str(e)
438
  }
439
 
440
  @router.get("/admin/dashboard")
441
  def get_admin_dashboard(
442
  user_token: str = Header(None, alias="User-key"),
443
- period: str = Query("all_time", description="Período: all_time, last_month, last_year"),
444
- top_stylists_limit: int = Query(10, ge=1, le=50)
445
  ):
446
  """
447
  Endpoint para dashboard administrativo com métricas de faturamento
@@ -475,16 +316,11 @@ def get_admin_dashboard(
475
  transfer_data["total_transferred_to_stylists"]
476
  )
477
 
478
- # Obter detalhamento mensal dos últimos 12 meses
479
- monthly_revenue = get_monthly_revenue_breakdown(12)
480
-
481
- # Obter top estilistas por faturamento
482
- top_stylists = get_top_earning_stylists(top_stylists_limit, start_timestamp, end_timestamp)
483
 
484
- # Obter métricas gerais da plataforma
485
- platform_metrics = get_platform_metrics()
486
-
487
- return {
488
  "total_revenue": revenue_data["successful_revenue"],
489
  "failed_revenue": revenue_data["failed_revenue"],
490
  "total_transferred_to_stylists": transfer_data["total_transferred_to_stylists"],
@@ -495,11 +331,17 @@ def get_admin_dashboard(
495
  "transfer_count": transfer_data["transfer_count"],
496
  "currency": "BRL",
497
  "period": period,
498
- "monthly_revenue": monthly_revenue,
499
- "top_earning_stylists": top_stylists,
500
- "platform_metrics": platform_metrics
501
  }
502
 
 
 
 
 
 
 
 
 
503
  except HTTPException as he:
504
  raise he
505
 
@@ -510,8 +352,7 @@ def get_admin_dashboard(
510
  @router.post("/admin/dashboard/custom_period")
511
  def get_custom_period_dashboard(
512
  date_range: DateRangeRequest,
513
- user_token: str = Header(None, alias="User-key"),
514
- top_stylists_limit: int = Query(10, ge=1, le=50)
515
  ):
516
  """
517
  Endpoint para dashboard administrativo com métricas de faturamento
@@ -545,8 +386,8 @@ def get_custom_period_dashboard(
545
  transfer_data["total_transferred_to_stylists"]
546
  )
547
 
548
- # Obter top estilistas por faturamento
549
- top_stylists = get_top_earning_stylists(top_stylists_limit, start_timestamp, end_timestamp)
550
 
551
  return {
552
  "total_revenue": revenue_data["successful_revenue"],
@@ -562,7 +403,7 @@ def get_custom_period_dashboard(
562
  "start_date": date_range.start_date,
563
  "end_date": date_range.end_date
564
  },
565
- "top_earning_stylists": top_stylists
566
  }
567
 
568
  except HTTPException as he:
 
6
  from fastapi import APIRouter, HTTPException, Header, Query
7
  from datetime import datetime, timedelta
8
  from dateutil.relativedelta import relativedelta
9
+ from typing import Dict, Any, Optional
10
  from pydantic import BaseModel
11
 
12
  router = APIRouter()
 
53
  if not user_id:
54
  raise HTTPException(status_code=400, detail="ID do usuário não encontrado")
55
 
56
+ # Verifica se o usuário é um administrador
57
  user_data_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
58
  response = requests.get(user_data_url, headers=SUPABASE_HEADERS)
59
 
 
71
  def get_total_platform_revenue(start_timestamp: int = None, end_timestamp: int = None) -> Dict[str, Any]:
72
  """Obtém o faturamento total da plataforma no Stripe"""
73
  try:
74
+ query_params = {"limit": 100}
 
 
75
 
76
  # Adicionar filtros de data se fornecidos
77
  if start_timestamp and end_timestamp:
78
+ query_params["created"] = {"gte": start_timestamp, "lte": end_timestamp}
 
 
 
79
  elif start_timestamp:
80
  query_params["created"] = {"gte": start_timestamp}
81
  elif end_timestamp:
 
90
  if last_id:
91
  query_params["starting_after"] = last_id
92
 
 
93
  payment_list = stripe.Charge.list(**query_params)
94
  payments.extend(payment_list.data)
95
 
 
106
 
107
  for payment in payments:
108
  amount = payment.amount
 
 
109
  total_revenue += amount
110
 
 
111
  if payment.status == 'succeeded':
112
  successful_revenue += amount
113
  elif payment.status == 'failed':
 
135
  def get_platform_transfers(start_timestamp: int = None, end_timestamp: int = None) -> Dict[str, Any]:
136
  """Obtém o total transferido para estilistas"""
137
  try:
138
+ query_params = {"limit": 100}
 
 
139
 
140
  # Adicionar filtros de data se fornecidos
141
  if start_timestamp and end_timestamp:
142
+ query_params["created"] = {"gte": start_timestamp, "lte": end_timestamp}
 
 
 
143
  elif start_timestamp:
144
  query_params["created"] = {"gte": start_timestamp}
145
  elif end_timestamp:
 
154
  if last_id:
155
  query_params["starting_after"] = last_id
156
 
 
157
  transfer_list = stripe.Transfer.list(**query_params)
158
  transfers.extend(transfer_list.data)
159
 
 
200
  "currency": "BRL"
201
  }
202
 
203
+ def get_monthly_revenue(target_date) -> Dict[str, Any]:
204
+ """Obtém o faturamento de um mês específico"""
205
  ny_timezone = pytz.timezone('America/New_York')
206
+ if not isinstance(target_date, datetime):
207
+ target_date = datetime.now(ny_timezone)
208
 
209
+ # Calcular início e fim do mês
210
+ month_start = datetime(target_date.year, target_date.month, 1, tzinfo=ny_timezone)
211
+ if target_date.month == 12:
212
+ month_end = datetime(target_date.year + 1, 1, 1, tzinfo=ny_timezone) - timedelta(seconds=1)
213
+ else:
214
+ month_end = datetime(target_date.year, target_date.month + 1, 1, tzinfo=ny_timezone) - timedelta(seconds=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
+ # Converter para timestamps
217
+ start_timestamp = int(month_start.timestamp())
218
+ end_timestamp = int(month_end.timestamp())
219
+
220
+ # Obter dados do mês
221
+ revenue_data = get_total_platform_revenue(start_timestamp, end_timestamp)
222
+ transfer_data = get_platform_transfers(start_timestamp, end_timestamp)
223
+
224
+ # Calcular divisão app/estilistas
225
+ total_revenue = revenue_data["successful_revenue"]
226
+ total_transferred = transfer_data["total_transferred_to_stylists"]
227
+ app_revenue = total_revenue - total_transferred
228
+
229
+ return {
230
+ "year": target_date.year,
231
+ "month": target_date.month,
232
+ "name": target_date.strftime("%b"),
233
+ "total_revenue": total_revenue,
234
+ "app_revenue": app_revenue,
235
+ "stylists_revenue": total_transferred,
236
+ "payment_count": revenue_data["payment_count"],
237
+ "transfer_count": transfer_data["transfer_count"]
238
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
+ def get_platform_users() -> Dict[str, Any]:
241
+ """Obtém informações sobre usuários e estilistas da plataforma"""
242
  try:
243
  # Obter número total de usuários
244
  users_url = f"{SUPABASE_URL}/rest/v1/User?select=count"
 
254
  total_users = int(count_header.split("/")[1])
255
 
256
  # Obter número total de estilistas
257
+ stylists_url = f"{SUPABASE_URL}/rest/v1/User?role=eq.stylist&select=count"
258
  stylists_response = requests.get(
259
  stylists_url,
260
  headers={**SUPABASE_HEADERS, "Prefer": "count=exact"}
 
266
  if count_header:
267
  total_stylists = int(count_header.split("/")[1])
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  return {
270
  "total_users": total_users,
271
+ "total_stylists": total_stylists
 
 
 
272
  }
273
 
274
  except Exception as e:
275
+ logger.error(f"❌ Erro ao obter informações de usuários: {str(e)}")
276
  return {
277
  "total_users": 0,
278
  "total_stylists": 0,
 
 
 
279
  "error": str(e)
280
  }
281
 
282
  @router.get("/admin/dashboard")
283
  def get_admin_dashboard(
284
  user_token: str = Header(None, alias="User-key"),
285
+ period: str = Query("all_time", description="Período: all_time, last_month, last_year")
 
286
  ):
287
  """
288
  Endpoint para dashboard administrativo com métricas de faturamento
 
316
  transfer_data["total_transferred_to_stylists"]
317
  )
318
 
319
+ # Obter dados de usuários
320
+ user_data = get_platform_users()
 
 
 
321
 
322
+ # Preparar resposta
323
+ response = {
 
 
324
  "total_revenue": revenue_data["successful_revenue"],
325
  "failed_revenue": revenue_data["failed_revenue"],
326
  "total_transferred_to_stylists": transfer_data["total_transferred_to_stylists"],
 
331
  "transfer_count": transfer_data["transfer_count"],
332
  "currency": "BRL",
333
  "period": period,
334
+ "users": user_data
 
 
335
  }
336
 
337
+ # Adicionar dados específicos de mês apenas para período last_month
338
+ if period == "last_month":
339
+ target_date = now_ny - relativedelta(months=1)
340
+ monthly_data = get_monthly_revenue(target_date)
341
+ response["monthly_data"] = monthly_data
342
+
343
+ return response
344
+
345
  except HTTPException as he:
346
  raise he
347
 
 
352
  @router.post("/admin/dashboard/custom_period")
353
  def get_custom_period_dashboard(
354
  date_range: DateRangeRequest,
355
+ user_token: str = Header(None, alias="User-key")
 
356
  ):
357
  """
358
  Endpoint para dashboard administrativo com métricas de faturamento
 
386
  transfer_data["total_transferred_to_stylists"]
387
  )
388
 
389
+ # Obter dados de usuários
390
+ user_data = get_platform_users()
391
 
392
  return {
393
  "total_revenue": revenue_data["successful_revenue"],
 
403
  "start_date": date_range.start_date,
404
  "end_date": date_range.end_date
405
  },
406
+ "users": user_data
407
  }
408
 
409
  except HTTPException as he: