value, ReceivableStatus::CANCELLED->value, ]; public function overview(array $unitIds = []): array { return [ 'enrollments' => $this->enrollments($unitIds), 'birthdays' => $this->birthdays($unitIds), 'open_tickets' => $this->openTickets($unitIds), 'pending_tasks' => $this->pendingTasks($unitIds), 'general_revenue' => $this->generalRevenue($unitIds), 'product_stock' => $this->productStock(), ]; } /** * Matrículas (alunos cadastrados) por mês nos últimos 6 meses. */ private function enrollments(array $unitIds): array { $start = Carbon::now()->startOfMonth()->subMonthsNoOverflow(5); // Agrupa por ano-mês de forma portável (independente do SGBD): puxa apenas // a data de cadastro no período e conta os meses em memória. $createdAt = DB::table('students') ->whereNull('deleted_at') ->where('created_at', '>=', $start) ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds)) ->pluck('created_at'); $counts = []; foreach ($createdAt as $date) { $ym = Carbon::parse($date)->format('Y-m'); $counts[$ym] = ($counts[$ym] ?? 0) + 1; } $labels = []; $data = []; $months = ['JAN', 'FEV', 'MAR', 'ABR', 'MAI', 'JUN', 'JUL', 'AGO', 'SET', 'OUT', 'NOV', 'DEZ']; for ($i = 0; $i < 6; $i++) { $month = $start->copy()->addMonthsNoOverflow($i); $labels[] = $months[$month->month - 1]; $data[] = (int) ($counts[$month->format('Y-m')] ?? 0); } return ['labels' => $labels, 'data' => $data]; } /** * Aniversariantes do mês corrente (dia + nome). */ private function birthdays(array $unitIds): array { return DB::table('students') ->whereNull('deleted_at') ->whereMonth('birth_date', Carbon::now()->month) ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds)) ->get(['name', 'birth_date']) ->map(fn ($s) => [ 'day' => (int) Carbon::parse($s->birth_date)->day, 'name' => $s->name, ]) ->sortBy('day') ->values() ->all(); } private function openTickets(array $unitIds): int { return (int) DB::table('support_tickets') ->whereNull('deleted_at') ->where('status', 'in_progress') ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds)) ->count(); } private function pendingTasks(array $unitIds): int { return (int) DB::table('kanbans') ->whereNull('deleted_at') ->where('phase', '!=', 'concluido') ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds)) ->count(); } /** * Receita Geral = total das cobranças às franquias (pago + em aberto), * com a contagem das que ainda estão pendentes. */ private function generalRevenue(array $unitIds): array { $base = DB::table('franchisee_account_receives') ->whereNull('deleted_at') ->when(!empty($unitIds), fn ($q) => $q->whereIn('unit_id', $unitIds)); $value = (float) (clone $base)->sum('value'); $pending = (int) (clone $base) ->whereNotIn('status', self::SETTLED) ->count(); return ['value' => $value, 'pending_count' => $pending]; } private function productStock(): int { return (int) DB::table('products') ->whereNull('deleted_at') ->sum('quantity'); } }