FranchisorDashboardService.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. <?php
  2. namespace App\Services;
  3. use App\Enums\ReceivableStatus;
  4. use Illuminate\Support\Carbon;
  5. use Illuminate\Support\Facades\DB;
  6. /**
  7. * Agrega os indicadores da tela inicial (Dashboard) da visão do Franqueador.
  8. *
  9. * Premissas de cálculo:
  10. * - "Matrículas por Período" = quantidade de alunos cadastrados por mês (created_at),
  11. * nos últimos 6 meses.
  12. * - "Aniversariantes do Mês" = alunos cujo mês de nascimento é o mês corrente.
  13. * - "Tickets Abertos" = chamados de suporte com status 'in_progress'.
  14. * - "Tarefas Pendentes" = cartões do Kanban que ainda não foram concluídos.
  15. * - "Receita Geral" = total cobrado das franquias (franchisee_account_receives),
  16. * pago + em aberto; a legenda traz a quantidade de pagamentos pendentes.
  17. * - "Estoque Geral de Produtos" = soma das quantidades em estoque dos produtos.
  18. *
  19. * Todas as métricas por unidade respeitam o filtro `unitIds` (vazio = rede inteira);
  20. * o estoque de produtos é do catálogo do franqueador (não filtra por unidade).
  21. */
  22. class FranchisorDashboardService
  23. {
  24. private const SETTLED = [
  25. ReceivableStatus::PAID->value,
  26. ReceivableStatus::CANCELLED->value,
  27. ];
  28. public function overview(array $unitIds = []): array
  29. {
  30. return [
  31. 'enrollments' => $this->enrollments($unitIds),
  32. 'birthdays' => $this->birthdays($unitIds),
  33. 'open_tickets' => $this->openTickets($unitIds),
  34. 'pending_tasks' => $this->pendingTasks($unitIds),
  35. 'general_revenue' => $this->generalRevenue($unitIds),
  36. 'product_stock' => $this->productStock(),
  37. ];
  38. }
  39. /**
  40. * Matrículas (alunos cadastrados) por mês nos últimos 6 meses.
  41. */
  42. private function enrollments(array $unitIds): array
  43. {
  44. $start = Carbon::now()->startOfMonth()->subMonthsNoOverflow(5);
  45. $counts = DB::table('students')
  46. ->whereNull('deleted_at')
  47. ->where('created_at', '>=', $start)
  48. ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds))
  49. ->selectRaw("DATE_FORMAT(created_at, '%Y-%m') AS ym, COUNT(*) AS total")
  50. ->groupBy('ym')
  51. ->pluck('total', 'ym');
  52. $labels = [];
  53. $data = [];
  54. $months = ['JAN', 'FEV', 'MAR', 'ABR', 'MAI', 'JUN', 'JUL', 'AGO', 'SET', 'OUT', 'NOV', 'DEZ'];
  55. for ($i = 0; $i < 6; $i++) {
  56. $month = $start->copy()->addMonthsNoOverflow($i);
  57. $labels[] = $months[$month->month - 1];
  58. $data[] = (int) ($counts[$month->format('Y-m')] ?? 0);
  59. }
  60. return ['labels' => $labels, 'data' => $data];
  61. }
  62. /**
  63. * Aniversariantes do mês corrente (dia + nome).
  64. */
  65. private function birthdays(array $unitIds): array
  66. {
  67. return DB::table('students')
  68. ->whereNull('deleted_at')
  69. ->whereMonth('birth_date', Carbon::now()->month)
  70. ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds))
  71. ->orderByRaw('DAYOFMONTH(birth_date)')
  72. ->get(['name', 'birth_date'])
  73. ->map(fn ($s) => [
  74. 'day' => (int) Carbon::parse($s->birth_date)->day,
  75. 'name' => $s->name,
  76. ])
  77. ->all();
  78. }
  79. private function openTickets(array $unitIds): int
  80. {
  81. return (int) DB::table('support_tickets')
  82. ->whereNull('deleted_at')
  83. ->where('status', 'in_progress')
  84. ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds))
  85. ->count();
  86. }
  87. private function pendingTasks(array $unitIds): int
  88. {
  89. return (int) DB::table('kanbans')
  90. ->whereNull('deleted_at')
  91. ->where('phase', '!=', 'concluido')
  92. ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds))
  93. ->count();
  94. }
  95. /**
  96. * Receita Geral = total das cobranças às franquias (pago + em aberto),
  97. * com a contagem das que ainda estão pendentes.
  98. */
  99. private function generalRevenue(array $unitIds): array
  100. {
  101. $base = DB::table('franchisee_account_receives')
  102. ->whereNull('deleted_at')
  103. ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds));
  104. $value = (float) (clone $base)->sum('value');
  105. $pending = (int) (clone $base)
  106. ->whereNotIn('status', self::SETTLED)
  107. ->count();
  108. return ['value' => $value, 'pending_count' => $pending];
  109. }
  110. private function productStock(): int
  111. {
  112. return (int) DB::table('products')
  113. ->whereNull('deleted_at')
  114. ->sum('quantity');
  115. }
  116. }