query('instructor_id'); $from = (string) $request->query('from'); $to = (string) $request->query('to'); if ($instructorId <= 0 || $from === '' || $to === '') { Response::json(['message' => 'instructor_id, from und to sind erforderlich'], 422); } $repo = new WorkTimesheetRepository(); $tenantId = (int) $user['tenant_id']; $instructor = $repo->getInstructor($tenantId, $instructorId); if ($instructor === null) { Response::json(['message' => 'Fahrlehrer nicht gefunden'], 404); } $appointments = $repo->getAppointmentsForInstructor($tenantId, $instructorId, $from, $to); // Gruppiere nach Datum $daysMap = []; foreach ($appointments as $apt) { $date = date('Y-m-d', strtotime($apt['start_at'])); if (!isset($daysMap[$date])) { $daysMap[$date] = []; } $daysMap[$date][] = $apt; } $days = []; $typeSummaryMap = []; foreach ($daysMap as $date => $aptList) { $dayTotalMinutes = 0; $dayTotalUe = 0.0; $appointmentsOut = []; foreach ($aptList as $apt) { $startAt = new \DateTimeImmutable($apt['start_at']); $endAt = new \DateTimeImmutable($apt['end_at']); $durationMinutes = (int) round(($endAt->getTimestamp() - $startAt->getTimestamp()) / 60); $ue = round((float) $apt['units'], 2); $appointmentsOut[] = [ 'type' => $apt['lesson_type_name'], 'start_at' => $startAt->format('H:i'), 'end_at' => $endAt->format('H:i'), 'duration_minutes' => $durationMinutes, 'ue' => $ue, 'ue_formatted' => self::formatUe($ue), ]; $dayTotalMinutes += $durationMinutes; $dayTotalUe += $ue; // Type summary $typeKey = $apt['lesson_type_name']; if (!isset($typeSummaryMap[$typeKey])) { $typeSummaryMap[$typeKey] = ['type' => $apt['lesson_type_name'], 'total_minutes' => 0, 'total_ue' => 0.0, 'count' => 0]; } $typeSummaryMap[$typeKey]['total_minutes'] += $durationMinutes; $typeSummaryMap[$typeKey]['total_ue'] += $ue; $typeSummaryMap[$typeKey]['count']++; } $days[] = [ 'date' => $date, 'appointments' => $appointmentsOut, 'day_total_minutes' => $dayTotalMinutes, 'day_total_ue' => round($dayTotalUe, 2), 'day_total_ue_formatted' => self::formatUe($dayTotalUe), ]; } // Gesamtsumme $totalMinutes = 0; $totalUe = 0.0; foreach ($typeSummaryMap as $entry) { $totalMinutes += $entry['total_minutes']; $totalUe += $entry['total_ue']; } // Typ-Zusammenfassung sortieren $typeSummary = array_values($typeSummaryMap); usort($typeSummary, static fn($a, $b) => $a['type'] <=> $b['type']); // UE formatiert foreach ($typeSummary as &$entry) { $entry['total_ue'] = round($entry['total_ue'], 2); $entry['total_ue_formatted'] = self::formatUe($entry['total_ue']); } Response::json([ 'data' => [ 'instructor' => $instructor, 'period' => ['from' => $from, 'to' => $to], 'days' => $days, 'type_summary' => $typeSummary, 'summary' => [ 'total_minutes' => $totalMinutes, 'total_ue' => round($totalUe, 2), 'total_ue_formatted' => self::formatUe($totalUe), ], ], ]); } private static function formatUe(float $ue): string { return str_replace('.', ',', number_format($ue, 2, ',', '')) . ' UE'; } }