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

Create dashboard_home.py

Browse files
Files changed (1) hide show
  1. routes/dashboard_home.py +573 -0
routes/dashboard_home.py ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import stripe
3
+ import requests
4
+ import logging
5
+ 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()
13
+
14
+ # Configuração das chaves do Stripe e Supabase
15
+ stripe.api_key = os.getenv("STRIPE_KEY")
16
+ stripe.api_version = "2023-10-16"
17
+ SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
18
+ SUPABASE_KEY = os.getenv("SUPA_KEY")
19
+
20
+ if not stripe.api_key or not SUPABASE_KEY:
21
+ raise ValueError("❌ STRIPE_KEY ou SUPA_KEY não foram definidos no ambiente!")
22
+
23
+ SUPABASE_HEADERS = {
24
+ "apikey": SUPABASE_KEY,
25
+ "Authorization": f"Bearer {SUPABASE_KEY}",
26
+ "Content-Type": "application/json"
27
+ }
28
+
29
+ # Configuração do logging
30
+ logging.basicConfig(level=logging.INFO)
31
+ logger = logging.getLogger(__name__)
32
+
33
+ # Modelo para requisições com período de tempo opcional
34
+ class DateRangeRequest(BaseModel):
35
+ start_date: Optional[str] = None # Formato: 'YYYY-MM-DD'
36
+ end_date: Optional[str] = None # Formato: 'YYYY-MM-DD'
37
+
38
+ def verify_admin_token(user_token: str) -> str:
39
+ """Verifica se o token pertence a um administrador"""
40
+ headers = {
41
+ "Authorization": f"Bearer {user_token}",
42
+ "apikey": SUPABASE_KEY,
43
+ "Content-Type": "application/json"
44
+ }
45
+
46
+ # Primeiro, verifica se o token é válido
47
+ response = requests.get(f"{SUPABASE_URL}/auth/v1/user", headers=headers)
48
+ if response.status_code != 200:
49
+ raise HTTPException(status_code=401, detail="Token inválido ou expirado")
50
+
51
+ user_data = response.json()
52
+ user_id = user_data.get("id")
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
+
60
+ if response.status_code != 200 or not response.json():
61
+ raise HTTPException(status_code=404, detail="Usuário não encontrado")
62
+
63
+ user_info = response.json()[0]
64
+ is_admin = user_info.get("is_admin", False)
65
+
66
+ if not is_admin:
67
+ raise HTTPException(status_code=403, detail="Acesso negado: privilégios de administrador necessários")
68
+
69
+ return user_id
70
+
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:
87
+ query_params["created"] = {"lte": end_timestamp}
88
+
89
+ payments = []
90
+ has_more = True
91
+ last_id = None
92
+
93
+ # Paginar resultados para obter todos os pagamentos
94
+ while has_more:
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
+
102
+ has_more = payment_list.has_more
103
+ if payment_list.data:
104
+ last_id = payment_list.data[-1].id
105
+ else:
106
+ has_more = False
107
+
108
+ # Calcular totais
109
+ total_revenue = 0
110
+ successful_revenue = 0
111
+ failed_revenue = 0
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':
123
+ failed_revenue += amount
124
+
125
+ return {
126
+ "total_revenue": total_revenue,
127
+ "successful_revenue": successful_revenue,
128
+ "failed_revenue": failed_revenue,
129
+ "currency": "BRL",
130
+ "payment_count": len(payments)
131
+ }
132
+
133
+ except Exception as e:
134
+ logger.error(f"❌ Erro ao obter faturamento total: {str(e)}")
135
+ return {
136
+ "total_revenue": 0,
137
+ "successful_revenue": 0,
138
+ "failed_revenue": 0,
139
+ "currency": "BRL",
140
+ "payment_count": 0,
141
+ "error": str(e)
142
+ }
143
+
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:
160
+ query_params["created"] = {"lte": end_timestamp}
161
+
162
+ transfers = []
163
+ has_more = True
164
+ last_id = None
165
+
166
+ # Paginar resultados para obter todas as transferências
167
+ while has_more:
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
+
175
+ has_more = transfer_list.has_more
176
+ if transfer_list.data:
177
+ last_id = transfer_list.data[-1].id
178
+ else:
179
+ has_more = False
180
+
181
+ # Calcular total transferido para estilistas
182
+ total_transferred = sum(transfer.amount for transfer in transfers)
183
+
184
+ return {
185
+ "total_transferred_to_stylists": total_transferred,
186
+ "currency": "BRL",
187
+ "transfer_count": len(transfers)
188
+ }
189
+
190
+ except Exception as e:
191
+ logger.error(f"❌ Erro ao obter transferências: {str(e)}")
192
+ return {
193
+ "total_transferred_to_stylists": 0,
194
+ "currency": "BRL",
195
+ "transfer_count": 0,
196
+ "error": str(e)
197
+ }
198
+
199
+ def get_app_revenue_share(total_revenue: int, total_transferred: int) -> Dict[str, Any]:
200
+ """Calcula a parte do faturamento que ficou com o aplicativo"""
201
+ app_revenue = total_revenue - total_transferred
202
+
203
+ # Calcular percentuais
204
+ if total_revenue > 0:
205
+ app_percentage = (app_revenue / total_revenue) * 100
206
+ stylists_percentage = (total_transferred / total_revenue) * 100
207
+ else:
208
+ app_percentage = 0
209
+ stylists_percentage = 0
210
+
211
+ return {
212
+ "app_revenue": app_revenue,
213
+ "app_percentage": round(app_percentage, 2),
214
+ "stylists_percentage": round(stylists_percentage, 2),
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"
368
+ users_response = requests.get(
369
+ users_url,
370
+ headers={**SUPABASE_HEADERS, "Prefer": "count=exact"}
371
+ )
372
+
373
+ total_users = 0
374
+ if users_response.status_code == 200:
375
+ count_header = users_response.headers.get("Content-Range", "")
376
+ if count_header:
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"}
384
+ )
385
+
386
+ total_stylists = 0
387
+ if stylists_response.status_code == 200:
388
+ count_header = stylists_response.headers.get("Content-Range", "")
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
448
+ e divisão entre app e estilistas
449
+ """
450
+ try:
451
+ # Verificar se é um administrador
452
+ user_id = verify_admin_token(user_token)
453
+
454
+ # Definir intervalo de datas baseado no período solicitado
455
+ ny_timezone = pytz.timezone('America/New_York')
456
+ now_ny = datetime.now(ny_timezone)
457
+
458
+ start_timestamp = None
459
+ end_timestamp = int(now_ny.timestamp())
460
+
461
+ if period == "last_month":
462
+ start_date = now_ny - relativedelta(months=1)
463
+ start_timestamp = int(start_date.timestamp())
464
+ elif period == "last_year":
465
+ start_date = now_ny - relativedelta(years=1)
466
+ start_timestamp = int(start_date.timestamp())
467
+
468
+ # Obter dados gerais de faturamento
469
+ revenue_data = get_total_platform_revenue(start_timestamp, end_timestamp)
470
+ transfer_data = get_platform_transfers(start_timestamp, end_timestamp)
471
+
472
+ # Calcular divisão de receita entre app e estilistas
473
+ revenue_share = get_app_revenue_share(
474
+ revenue_data["successful_revenue"],
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"],
491
+ "app_revenue": revenue_share["app_revenue"],
492
+ "app_revenue_percentage": revenue_share["app_percentage"],
493
+ "stylists_revenue_percentage": revenue_share["stylists_percentage"],
494
+ "payment_count": revenue_data["payment_count"],
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
+
506
+ except Exception as e:
507
+ logger.error(f"❌ Erro ao gerar dashboard administrativo: {str(e)}")
508
+ raise HTTPException(status_code=500, detail=str(e))
509
+
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
518
+ para um período personalizado
519
+ """
520
+ try:
521
+ # Verificar se é um administrador
522
+ user_id = verify_admin_token(user_token)
523
+
524
+ # Converter datas para timestamps
525
+ start_timestamp = None
526
+ end_timestamp = None
527
+
528
+ if date_range.start_date:
529
+ start_date = datetime.fromisoformat(date_range.start_date)
530
+ start_timestamp = int(start_date.timestamp())
531
+
532
+ if date_range.end_date:
533
+ # Se temos apenas a data, queremos incluir o dia inteiro
534
+ end_date = datetime.fromisoformat(date_range.end_date)
535
+ end_date = datetime(end_date.year, end_date.month, end_date.day, 23, 59, 59)
536
+ end_timestamp = int(end_date.timestamp())
537
+
538
+ # Obter dados gerais de faturamento
539
+ revenue_data = get_total_platform_revenue(start_timestamp, end_timestamp)
540
+ transfer_data = get_platform_transfers(start_timestamp, end_timestamp)
541
+
542
+ # Calcular divisão de receita entre app e estilistas
543
+ revenue_share = get_app_revenue_share(
544
+ revenue_data["successful_revenue"],
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"],
553
+ "failed_revenue": revenue_data["failed_revenue"],
554
+ "total_transferred_to_stylists": transfer_data["total_transferred_to_stylists"],
555
+ "app_revenue": revenue_share["app_revenue"],
556
+ "app_revenue_percentage": revenue_share["app_percentage"],
557
+ "stylists_revenue_percentage": revenue_share["stylists_percentage"],
558
+ "payment_count": revenue_data["payment_count"],
559
+ "transfer_count": transfer_data["transfer_count"],
560
+ "currency": "BRL",
561
+ "period": {
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:
569
+ raise he
570
+
571
+ except Exception as e:
572
+ logger.error(f"❌ Erro ao gerar dashboard para período personalizado: {str(e)}")
573
+ raise HTTPException(status_code=500, detail=str(e))