Files
drivetimeplaner/www/fahrschultermin.de/app/Repositories/StudentRepository.php

295 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Repositories;
final class StudentRepository extends BaseRepository
{
public function allForTenant(int $tenantId): array
{
$statement = $this->db->prepare(
'SELECT s.*, t.code AS template_code, t.name AS template_name, t.is_combination AS template_is_combination
FROM students s
JOIN license_class_templates t ON t.id = s.template_id
WHERE s.tenant_id = :tenant_id
ORDER BY s.last_name, s.first_name'
);
$statement->execute(['tenant_id' => $tenantId]);
return $statement->fetchAll();
}
public function find(int $tenantId, int $id): ?array
{
$statement = $this->db->prepare(
'SELECT s.*, t.code AS template_code, t.name AS template_name, t.is_combination AS template_is_combination
FROM students s
JOIN license_class_templates t ON t.id = s.template_id
WHERE s.tenant_id = :tenant_id AND s.id = :id'
);
$statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
$student = $statement->fetch() ?: null;
if ($student !== null) {
$student['requirements'] = $this->requirements($id);
}
return $student;
}
public function create(int $tenantId, array $data): array
{
$timestamp = $this->now();
$statement = $this->db->prepare(
'INSERT INTO students
(tenant_id, template_id, first_name, last_name, phone, email, needs_glasses, notes, status, created_at, updated_at)
VALUES (:tenant_id, :template_id, :first_name, :last_name, :phone, :email, :needs_glasses, :notes, :status, :created_at, :updated_at)'
);
$statement->execute([
'tenant_id' => $tenantId,
'template_id' => $data['template_id'],
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? '',
'email' => $data['email'] ?? '',
'needs_glasses' => !empty($data['needs_glasses']) ? 1 : 0,
'notes' => $data['notes'] ?? '',
'status' => $data['status'] ?? 'active',
'created_at' => $timestamp,
'updated_at' => $timestamp,
]);
$studentId = (int) $this->db->lastInsertId();
$this->snapshotRequirements($studentId, (int) $data['template_id']);
return $this->find($tenantId, $studentId);
}
public function update(int $tenantId, int $id, array $data): ?array
{
$statement = $this->db->prepare(
'UPDATE students
SET template_id = :template_id, first_name = :first_name, last_name = :last_name, phone = :phone, email = :email, needs_glasses = :needs_glasses,
notes = :notes, status = :status, updated_at = :updated_at
WHERE tenant_id = :tenant_id AND id = :id'
);
$statement->execute([
'tenant_id' => $tenantId,
'id' => $id,
'template_id' => $data['template_id'],
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? '',
'email' => $data['email'] ?? '',
'needs_glasses' => !empty($data['needs_glasses']) ? 1 : 0,
'notes' => $data['notes'] ?? '',
'status' => $data['status'] ?? 'active',
'updated_at' => $this->now(),
]);
return $this->find($tenantId, $id);
}
public function delete(int $tenantId, int $id): bool
{
$student = $this->find($tenantId, $id);
if ($student === null) {
return false;
}
$this->db->beginTransaction();
try {
$updateAppointments = $this->db->prepare(
'UPDATE appointments SET student_id = NULL, updated_at = :updated_at WHERE tenant_id = :tenant_id AND student_id = :student_id'
);
$updateAppointments->execute([
'tenant_id' => $tenantId,
'student_id' => $id,
'updated_at' => $this->now(),
]);
$deleteSnapshots = $this->db->prepare('DELETE FROM student_requirement_snapshots WHERE student_id = :student_id');
$deleteSnapshots->execute(['student_id' => $id]);
$deleteStudent = $this->db->prepare('DELETE FROM students WHERE tenant_id = :tenant_id AND id = :id');
$deleteStudent->execute(['tenant_id' => $tenantId, 'id' => $id]);
$this->db->commit();
return true;
} catch (\Throwable $exception) {
$this->db->rollBack();
throw $exception;
}
}
public function requirements(int $studentId): array
{
$this->syncRequirementSnapshots($studentId);
$statement = $this->db->prepare(
'SELECT * FROM student_requirement_snapshots WHERE student_id = :student_id ORDER BY sort_order, phase_label, label'
);
$statement->execute(['student_id' => $studentId]);
return $statement->fetchAll();
}
public function updatePriorCompletedUnits(int $tenantId, int $studentId, array $rows): ?array
{
$student = $this->find($tenantId, $studentId);
if ($student === null) {
return null;
}
$statement = $this->db->prepare(
'UPDATE student_requirement_snapshots
SET prior_completed_units = :prior_completed_units
WHERE student_id = :student_id AND requirement_key = :requirement_key'
);
foreach ($rows as $row) {
$statement->execute([
'student_id' => $studentId,
'requirement_key' => (string) ($row['requirement_key'] ?? ''),
'prior_completed_units' => max(0, (int) ($row['prior_completed_units'] ?? 0)),
]);
}
return $this->find($tenantId, $studentId);
}
private function snapshotRequirements(int $studentId, int $templateId): void
{
$this->syncRequirementSnapshots($studentId, $templateId);
}
private function syncRequirementSnapshots(int $studentId, ?int $templateId = null): void
{
if ($templateId === null) {
$student = $this->db->prepare('SELECT template_id FROM students WHERE id = :student_id');
$student->execute(['student_id' => $studentId]);
$templateId = (int) ($student->fetch()['template_id'] ?? 0);
}
if ($templateId <= 0) {
return;
}
$requirements = $this->db->prepare(
'SELECT requirement_key, label, required_units, sort_order, phase_label
FROM license_template_requirements WHERE template_id = :template_id'
);
$requirements->execute(['template_id' => $templateId]);
$rows = [];
foreach ($requirements->fetchAll() as $row) {
$rows[$row['requirement_key']] = $row;
}
$hasPhasedRequirements = array_reduce(
$rows,
static fn (bool $carry, array $row): bool => $carry || trim((string) ($row['phase_label'] ?? '')) !== '',
false
);
$lessonTypes = $this->db->prepare(
'SELECT COALESCE(NULLIF(TRIM(lt.requirement_key), \'\'), \'lesson_type_\' || lt.id) AS requirement_key,
lt.name AS label,
lt.sort_order
FROM lesson_types lt
JOIN lesson_type_template_visibility vis ON vis.lesson_type_id = lt.id
WHERE vis.template_id = :template_id
AND lt.category = \'student\'
AND lt.is_counted = 1
ORDER BY lt.sort_order, lt.name'
);
$lessonTypes->execute(['template_id' => $templateId]);
foreach ($lessonTypes->fetchAll() as $row) {
if (isset($rows[$row['requirement_key']])) {
continue;
}
if ($hasPhasedRequirements) {
$hasMatchingPhasedRequirement = false;
foreach ($rows as $existingRow) {
$phaseLabel = trim((string) ($existingRow['phase_label'] ?? ''));
if ($phaseLabel === '') {
continue;
}
if (str_ends_with((string) $existingRow['requirement_key'], '::' . $row['requirement_key'])) {
$hasMatchingPhasedRequirement = true;
break;
}
}
if ($hasMatchingPhasedRequirement) {
continue;
}
}
$rows[$row['requirement_key']] = [
'requirement_key' => $row['requirement_key'],
'label' => $row['label'],
'required_units' => 0,
'sort_order' => $row['sort_order'],
];
}
$insert = $this->db->prepare(
'INSERT INTO student_requirement_snapshots
(student_id, requirement_key, label, required_units, prior_completed_units, sort_order, phase_label)
SELECT :student_id, :requirement_key, :label, :required_units, 0, :sort_order, :phase_label
WHERE NOT EXISTS (
SELECT 1 FROM student_requirement_snapshots
WHERE student_id = :student_id AND requirement_key = :requirement_key
)'
);
$update = $this->db->prepare(
'UPDATE student_requirement_snapshots
SET label = :label,
required_units = :required_units,
sort_order = :sort_order,
phase_label = :phase_label
WHERE student_id = :student_id AND requirement_key = :requirement_key'
);
foreach ($rows as $row) {
$insert->execute([
'student_id' => $studentId,
'requirement_key' => $row['requirement_key'],
'label' => $row['label'],
'required_units' => $row['required_units'],
'sort_order' => $row['sort_order'],
'phase_label' => $row['phase_label'] ?? '',
]);
$update->execute([
'student_id' => $studentId,
'requirement_key' => $row['requirement_key'],
'label' => $row['label'],
'required_units' => $row['required_units'],
'sort_order' => $row['sort_order'],
'phase_label' => $row['phase_label'] ?? '',
]);
}
if ($rows !== []) {
$placeholders = implode(', ', array_fill(0, count($rows), '?'));
$delete = $this->db->prepare(
"DELETE FROM student_requirement_snapshots
WHERE student_id = ?
AND requirement_key NOT IN ($placeholders)
AND prior_completed_units = 0
AND NOT EXISTS (
SELECT 1 FROM appointment_units au
JOIN appointments a ON a.id = au.appointment_id
WHERE a.student_id = student_requirement_snapshots.student_id
AND au.requirement_key = student_requirement_snapshots.requirement_key
)"
);
$delete->execute(array_merge([$studentId], array_keys($rows)));
}
}
}