Backup: old custom PHP API code removed from fahrschultermin.de
This commit is contained in:
93
backup/old_api_20260520/app/Services/CalendarService.php
Normal file
93
backup/old_api_20260520/app/Services/CalendarService.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Repositories\AppointmentRepository;
|
||||
use App\Repositories\ReferenceDataRepository;
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final class CalendarService
|
||||
{
|
||||
public function validate(int $tenantId, array $payload, ?int $appointmentId = null): array
|
||||
{
|
||||
$appointmentRepository = new AppointmentRepository();
|
||||
$lessonType = (new ReferenceDataRepository())->findLessonType($tenantId, (int) $payload['lesson_type_id']);
|
||||
$warnings = [
|
||||
'conflicts' => [],
|
||||
'rest_period_warnings' => [],
|
||||
];
|
||||
|
||||
$conflicts = $appointmentRepository->overlappingForInstructor(
|
||||
$tenantId,
|
||||
(int) $payload['instructor_id'],
|
||||
$payload['start_at'],
|
||||
$payload['end_at'],
|
||||
$appointmentId
|
||||
);
|
||||
|
||||
foreach ($conflicts as $conflict) {
|
||||
$warnings['conflicts'][] = [
|
||||
'type' => 'instructor_overlap',
|
||||
'message' => 'Fahrlehrer ist in diesem Zeitraum bereits verplant.',
|
||||
'appointment_id' => (int) $conflict['id'],
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($payload['student_id'])) {
|
||||
$studentConflicts = $appointmentRepository->overlappingForStudent(
|
||||
$tenantId,
|
||||
(int) $payload['student_id'],
|
||||
$payload['start_at'],
|
||||
$payload['end_at'],
|
||||
$appointmentId
|
||||
);
|
||||
foreach ($studentConflicts as $conflict) {
|
||||
$warnings['conflicts'][] = [
|
||||
'type' => 'student_overlap',
|
||||
'message' => 'Fahrschueler ist in diesem Zeitraum bereits verplant.',
|
||||
'appointment_id' => (int) $conflict['id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($lessonType !== null && (int) $lessonType['is_rest_relevant'] === 1) {
|
||||
$newStart = new DateTimeImmutable($payload['start_at']);
|
||||
$dayStart = $newStart->setTime(0, 0, 0);
|
||||
$dayEnd = $dayStart->modify('+1 day');
|
||||
$hasLaterRelevantOnDay = $appointmentRepository->hasRestRelevantAfterStartOnDay(
|
||||
$tenantId,
|
||||
(int) $payload['instructor_id'],
|
||||
$dayStart->format(DATE_ATOM),
|
||||
$dayEnd->format(DATE_ATOM),
|
||||
$payload['start_at']
|
||||
);
|
||||
|
||||
if ($hasLaterRelevantOnDay) {
|
||||
return $warnings;
|
||||
}
|
||||
|
||||
$lastRelevant = $appointmentRepository->lastRestRelevantForInstructor(
|
||||
$tenantId,
|
||||
(int) $payload['instructor_id'],
|
||||
$payload['start_at'],
|
||||
$appointmentId
|
||||
);
|
||||
if ($lastRelevant !== null) {
|
||||
$restEnds = (new DateTimeImmutable($lastRelevant['end_at']))->add(new DateInterval('PT11H'));
|
||||
if ($newStart < $restEnds) {
|
||||
$warnings['rest_period_warnings'][] = [
|
||||
'type' => 'rest_period',
|
||||
'message' => 'Achtung: Dieser Termin liegt innerhalb der 11-Stunden-Ruhezeit.',
|
||||
'rest_until' => $restEnds->format(DATE_ATOM),
|
||||
'blocking_appointment_id' => (int) $lastRelevant['id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $warnings;
|
||||
}
|
||||
}
|
||||
109
backup/old_api_20260520/app/Services/HolidayService.php
Normal file
109
backup/old_api_20260520/app/Services/HolidayService.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
|
||||
final class HolidayService
|
||||
{
|
||||
private const STATES_WITH_EPIPHANY = ['BW', 'BY', 'ST'];
|
||||
private const STATES_WITH_WOMENS_DAY = ['BE', 'MV'];
|
||||
private const STATES_WITH_CORPUS_CHRISTI = ['BW', 'BY', 'HE', 'NW', 'RP', 'SL'];
|
||||
private const STATES_WITH_ASSUMPTION = ['BY', 'SL'];
|
||||
private const STATES_WITH_REFORMATION = ['BB', 'HB', 'HH', 'MV', 'NI', 'SN', 'ST', 'SH', 'TH'];
|
||||
private const STATES_WITH_ALL_SAINTS = ['BW', 'BY', 'NW', 'RP', 'SL'];
|
||||
private const STATES_WITH_REPENTANCE = ['SN'];
|
||||
|
||||
public function forRange(array $tenant, string $from, string $to): array
|
||||
{
|
||||
$timezone = new DateTimeZone((string) ($tenant['timezone'] ?? 'Europe/Berlin'));
|
||||
$state = strtoupper((string) ($tenant['federal_state'] ?? 'BE'));
|
||||
$start = (new DateTimeImmutable($from, new DateTimeZone('UTC')))->setTimezone($timezone)->setTime(0, 0);
|
||||
$end = (new DateTimeImmutable($to, new DateTimeZone('UTC')))->setTimezone($timezone)->setTime(0, 0);
|
||||
|
||||
$years = [];
|
||||
$day = $start;
|
||||
while ($day <= $end) {
|
||||
$years[(int) $day->format('Y')] = true;
|
||||
$day = $day->add(new DateInterval('P1D'));
|
||||
}
|
||||
|
||||
$holidaysByDate = [];
|
||||
foreach (array_keys($years) as $year) {
|
||||
foreach ($this->holidaysForYear((int) $year, $state, $timezone) as $date => $name) {
|
||||
$holidaysByDate[$date] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$day = $start;
|
||||
while ($day < $end) {
|
||||
$key = $day->format('Y-m-d');
|
||||
$result[$key] = [
|
||||
'isSunday' => (int) $day->format('N') === 7,
|
||||
'holidayName' => $holidaysByDate[$key] ?? null,
|
||||
];
|
||||
$day = $day->add(new DateInterval('P1D'));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function holidaysForYear(int $year, string $state, DateTimeZone $timezone): array
|
||||
{
|
||||
$easter = (new DateTimeImmutable('@' . (string) easter_date($year)))->setTimezone($timezone)->setTime(0, 0);
|
||||
$holidays = [
|
||||
$this->dateKey($year, 1, 1, $timezone) => 'Neujahr',
|
||||
$easter->sub(new DateInterval('P2D'))->format('Y-m-d') => 'Karfreitag',
|
||||
$easter->add(new DateInterval('P1D'))->format('Y-m-d') => 'Ostermontag',
|
||||
$this->dateKey($year, 5, 1, $timezone) => 'Tag der Arbeit',
|
||||
$easter->add(new DateInterval('P39D'))->format('Y-m-d') => 'Christi Himmelfahrt',
|
||||
$easter->add(new DateInterval('P50D'))->format('Y-m-d') => 'Pfingstmontag',
|
||||
$this->dateKey($year, 10, 3, $timezone) => 'Tag der Deutschen Einheit',
|
||||
$this->dateKey($year, 12, 25, $timezone) => '1. Weihnachtstag',
|
||||
$this->dateKey($year, 12, 26, $timezone) => '2. Weihnachtstag',
|
||||
];
|
||||
|
||||
if (in_array($state, self::STATES_WITH_EPIPHANY, true)) {
|
||||
$holidays[$this->dateKey($year, 1, 6, $timezone)] = 'Heilige Drei Koenige';
|
||||
}
|
||||
if (in_array($state, self::STATES_WITH_WOMENS_DAY, true)) {
|
||||
$holidays[$this->dateKey($year, 3, 8, $timezone)] = 'Internationaler Frauentag';
|
||||
}
|
||||
if (in_array($state, self::STATES_WITH_CORPUS_CHRISTI, true)) {
|
||||
$holidays[$easter->add(new DateInterval('P60D'))->format('Y-m-d')] = 'Fronleichnam';
|
||||
}
|
||||
if (in_array($state, self::STATES_WITH_ASSUMPTION, true)) {
|
||||
$holidays[$this->dateKey($year, 8, 15, $timezone)] = 'Mariae Himmelfahrt';
|
||||
}
|
||||
if (in_array($state, self::STATES_WITH_REFORMATION, true)) {
|
||||
$holidays[$this->dateKey($year, 10, 31, $timezone)] = 'Reformationstag';
|
||||
}
|
||||
if (in_array($state, self::STATES_WITH_ALL_SAINTS, true)) {
|
||||
$holidays[$this->dateKey($year, 11, 1, $timezone)] = 'Allerheiligen';
|
||||
}
|
||||
if (in_array($state, self::STATES_WITH_REPENTANCE, true)) {
|
||||
$holidays[$this->repentanceDay($year, $timezone)->format('Y-m-d')] = 'Buss- und Bettag';
|
||||
}
|
||||
|
||||
return $holidays;
|
||||
}
|
||||
|
||||
private function dateKey(int $year, int $month, int $day, DateTimeZone $timezone): string
|
||||
{
|
||||
return (new DateTimeImmutable(sprintf('%04d-%02d-%02d', $year, $month, $day), $timezone))->format('Y-m-d');
|
||||
}
|
||||
|
||||
private function repentanceDay(int $year, DateTimeZone $timezone): DateTimeImmutable
|
||||
{
|
||||
$day = new DateTimeImmutable(sprintf('%04d-11-23', $year), $timezone);
|
||||
while ((int) $day->format('N') !== 3) {
|
||||
$day = $day->sub(new DateInterval('P1D'));
|
||||
}
|
||||
return $day;
|
||||
}
|
||||
}
|
||||
56
backup/old_api_20260520/app/Services/RequirementService.php
Normal file
56
backup/old_api_20260520/app/Services/RequirementService.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Repositories\AppointmentRepository;
|
||||
use App\Repositories\StudentRepository;
|
||||
use App\Support\Database;
|
||||
|
||||
final class RequirementService
|
||||
{
|
||||
public function progressForStudent(int $studentId): array
|
||||
{
|
||||
$db = Database::connection();
|
||||
$snapshots = (new StudentRepository())->requirements($studentId);
|
||||
|
||||
$statement = $db->prepare(
|
||||
'SELECT au.requirement_key,
|
||||
SUM(CASE WHEN a.status = "planned" THEN au.units_counted ELSE 0 END) AS planned_units,
|
||||
SUM(CASE WHEN a.status = "completed" THEN au.units_counted ELSE 0 END) AS completed_units
|
||||
FROM appointment_units au
|
||||
JOIN appointments a ON a.id = au.appointment_id
|
||||
WHERE a.student_id = :student_id
|
||||
GROUP BY au.requirement_key'
|
||||
);
|
||||
$statement->execute(['student_id' => $studentId]);
|
||||
$totals = [];
|
||||
foreach ($statement->fetchAll() as $row) {
|
||||
$totals[$row['requirement_key']] = [
|
||||
'planned' => (int) $row['planned_units'],
|
||||
'completed' => (int) $row['completed_units'],
|
||||
];
|
||||
}
|
||||
|
||||
return array_map(static function (array $snapshot) use ($totals): array {
|
||||
$row = $totals[$snapshot['requirement_key']] ?? ['planned' => 0, 'completed' => 0];
|
||||
$priorCompleted = (int) ($snapshot['prior_completed_units'] ?? 0);
|
||||
$appointmentCompleted = (int) $row['completed'];
|
||||
$completed = $priorCompleted + $appointmentCompleted;
|
||||
$open = (int) $snapshot['required_units'] - $row['planned'] - $completed;
|
||||
|
||||
return [
|
||||
'requirement_key' => $snapshot['requirement_key'],
|
||||
'phase_label' => $snapshot['phase_label'] ?? '',
|
||||
'label' => $snapshot['label'],
|
||||
'required_units' => (int) $snapshot['required_units'],
|
||||
'planned_units' => $row['planned'],
|
||||
'completed_units' => $completed,
|
||||
'prior_completed_units' => $priorCompleted,
|
||||
'appointment_completed_units' => $appointmentCompleted,
|
||||
'open_units' => $open,
|
||||
];
|
||||
}, $snapshots);
|
||||
}
|
||||
}
|
||||
55
backup/old_api_20260520/app/Services/SunTimesService.php
Normal file
55
backup/old_api_20260520/app/Services/SunTimesService.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
|
||||
final class SunTimesService
|
||||
{
|
||||
public function forRange(array $tenant, string $from, string $to): array
|
||||
{
|
||||
$latitude = isset($tenant['latitude']) ? (float) $tenant['latitude'] : null;
|
||||
$longitude = isset($tenant['longitude']) ? (float) $tenant['longitude'] : null;
|
||||
$timezoneName = (string) ($tenant['timezone'] ?? 'Europe/Berlin');
|
||||
|
||||
if ($latitude === null || $longitude === null || $latitude === 0.0 && $longitude === 0.0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$timezone = new DateTimeZone($timezoneName);
|
||||
$start = new DateTimeImmutable($from, new DateTimeZone('UTC'));
|
||||
$end = new DateTimeImmutable($to, new DateTimeZone('UTC'));
|
||||
$day = $start->setTimezone($timezone)->setTime(0, 0);
|
||||
$lastDay = $end->setTimezone($timezone)->setTime(0, 0);
|
||||
|
||||
$result = [];
|
||||
while ($day < $lastDay) {
|
||||
$solar = date_sun_info($day->setTime(12, 0)->getTimestamp(), $latitude, $longitude);
|
||||
$key = $day->format('Y-m-d');
|
||||
|
||||
$result[$key] = [
|
||||
'sunrise' => $this->formatSolarTimestamp($solar['sunrise'] ?? false, $timezone),
|
||||
'sunset' => $this->formatSolarTimestamp($solar['sunset'] ?? false, $timezone),
|
||||
];
|
||||
|
||||
$day = $day->add(new DateInterval('P1D'));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function formatSolarTimestamp(int|float|bool $timestamp, DateTimeZone $timezone): ?string
|
||||
{
|
||||
if (!is_int($timestamp) && !is_float($timestamp)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (new DateTimeImmutable('@' . (string) (int) $timestamp))
|
||||
->setTimezone($timezone)
|
||||
->format('H:i');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user