Slim API deployment - composer install + full CRUD endpoints
This commit is contained in:
@@ -96,6 +96,11 @@ final class AppointmentsController
|
||||
|
||||
$student = null;
|
||||
if (!empty($payload['student_id'])) {
|
||||
// Reject student_id if lesson_type does not allow it
|
||||
if ((int) $lessonType['allows_student'] !== 1) {
|
||||
Response::json(['message' => 'Bei dieser Stundenart kann kein Fahrschüler ausgewählt werden'], 422);
|
||||
}
|
||||
|
||||
$student = (new StudentRepository())->find($tenantId, (int) $payload['student_id']);
|
||||
if ($student === null) {
|
||||
Response::json(['message' => 'Unbekannter Fahrschueler'], 422);
|
||||
@@ -154,6 +159,7 @@ final class AppointmentsController
|
||||
'units' => $units,
|
||||
'status' => $payload['status'] ?? 'planned',
|
||||
'notes' => $payload['notes'] ?? '',
|
||||
'notes_private' => $payload['notes_private'] ?? '',
|
||||
'warning_acknowledged' => !empty($payload['warning_acknowledged']),
|
||||
'units_breakdown' => $unitsBreakdown,
|
||||
];
|
||||
|
||||
@@ -10,7 +10,7 @@ final class AppointmentRepository extends BaseRepository
|
||||
{
|
||||
$sql = 'SELECT a.*, s.first_name AS student_first_name, s.last_name AS student_last_name, s.needs_glasses AS student_needs_glasses,
|
||||
i.first_name AS instructor_first_name, i.last_name AS instructor_last_name,
|
||||
lt.name AS lesson_type_name, lt.color AS lesson_type_color, lt.is_rest_relevant,
|
||||
lt.name AS lesson_type_name, lt.color AS lesson_type_color, lt.is_rest_relevant, lt.allows_student, lt.category AS lesson_type_category,
|
||||
(
|
||||
SELECT au.requirement_key
|
||||
FROM appointment_units au
|
||||
@@ -61,9 +61,9 @@ final class AppointmentRepository extends BaseRepository
|
||||
$timestamp = $this->now();
|
||||
$statement = $this->db->prepare(
|
||||
'INSERT INTO appointments
|
||||
(tenant_id, student_id, instructor_id, lesson_type_id, title, category, start_at, end_at, units, status, notes, warning_acknowledged, created_by, updated_by, created_at, updated_at)
|
||||
(tenant_id, student_id, instructor_id, lesson_type_id, title, category, start_at, end_at, units, status, notes, notes_private, warning_acknowledged, created_by, updated_by, created_at, updated_at)
|
||||
VALUES
|
||||
(:tenant_id, :student_id, :instructor_id, :lesson_type_id, :title, :category, :start_at, :end_at, :units, :status, :notes, :warning_acknowledged, :created_by, :updated_by, :created_at, :updated_at)'
|
||||
(:tenant_id, :student_id, :instructor_id, :lesson_type_id, :title, :category, :start_at, :end_at, :units, :status, :notes, :notes_private, :warning_acknowledged, :created_by, :updated_by, :created_at, :updated_at)'
|
||||
);
|
||||
$statement->execute([
|
||||
'tenant_id' => $tenantId,
|
||||
@@ -77,6 +77,7 @@ final class AppointmentRepository extends BaseRepository
|
||||
'units' => (int) $data['units'],
|
||||
'status' => $data['status'],
|
||||
'notes' => $data['notes'] ?? '',
|
||||
'notes_private' => $data['notes_private'] ?? '',
|
||||
'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0,
|
||||
'created_by' => $actorId,
|
||||
'updated_by' => $actorId,
|
||||
@@ -96,7 +97,7 @@ final class AppointmentRepository extends BaseRepository
|
||||
'UPDATE appointments
|
||||
SET student_id = :student_id, instructor_id = :instructor_id, lesson_type_id = :lesson_type_id,
|
||||
title = :title, category = :category, start_at = :start_at, end_at = :end_at, units = :units,
|
||||
status = :status, notes = :notes, warning_acknowledged = :warning_acknowledged,
|
||||
status = :status, notes = :notes, notes_private = :notes_private, warning_acknowledged = :warning_acknowledged,
|
||||
updated_by = :updated_by, updated_at = :updated_at
|
||||
WHERE tenant_id = :tenant_id AND id = :id'
|
||||
);
|
||||
@@ -113,6 +114,7 @@ final class AppointmentRepository extends BaseRepository
|
||||
'units' => (int) $data['units'],
|
||||
'status' => $data['status'],
|
||||
'notes' => $data['notes'] ?? '',
|
||||
'notes_private' => $data['notes_private'] ?? '',
|
||||
'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0,
|
||||
'updated_by' => $actorId,
|
||||
'updated_at' => $this->now(),
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class AppointmentRequestRepository extends BaseRepository
|
||||
{
|
||||
public function create(
|
||||
int $studentUserId,
|
||||
int $instructorId,
|
||||
int $lessonTypeId,
|
||||
string $start,
|
||||
string $end,
|
||||
string $notes = ''
|
||||
): array {
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO appointment_requests
|
||||
(student_user_id, instructor_id, lesson_type_id, requested_start, requested_end, notes)
|
||||
VALUES (:student_user_id, :instructor_id, :lesson_type_id, :start, :end, :notes)
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'student_user_id' => $studentUserId,
|
||||
'instructor_id' => $instructorId,
|
||||
'lesson_type_id' => $lessonTypeId,
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'notes' => $notes,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function findById(int $id): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare('SELECT * FROM appointment_requests WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
$result = $stmt->fetch();
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
public function getPendingForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT ar.*,
|
||||
u.first_name AS student_first_name, u.last_name AS student_last_name,
|
||||
lt.name AS lesson_type_name, lt.category
|
||||
FROM appointment_requests ar
|
||||
JOIN users u ON u.id = ar.student_user_id
|
||||
JOIN lesson_types lt ON lt.id = ar.lesson_type_id
|
||||
WHERE ar.instructor_id = :instructor_id AND ar.status = \'pending\'
|
||||
ORDER BY ar.requested_start ASC'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getForStudent(int $studentUserId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT ar.*, lt.name AS lesson_type_name
|
||||
FROM appointment_requests ar
|
||||
JOIN lesson_types lt ON lt.id = ar.lesson_type_id
|
||||
WHERE ar.student_user_id = :student_user_id
|
||||
ORDER BY ar.requested_start DESC'
|
||||
);
|
||||
$stmt->execute(['student_user_id' => $studentUserId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function confirm(int $id, int $responseBy, int $appointmentId): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE appointment_requests
|
||||
SET status = \'confirmed\', responded_at = NOW(), response_by = :response_by,
|
||||
confirmed_appointment_id = :appointment_id
|
||||
WHERE id = :id AND status = \'pending\'
|
||||
RETURNING id'
|
||||
);
|
||||
$stmt->execute(['id' => $id, 'response_by' => $responseBy, 'appointment_id' => $appointmentId]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function reject(int $id, int $responseBy): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE appointment_requests
|
||||
SET status = \'rejected\', responded_at = NOW(), response_by = :response_by
|
||||
WHERE id = :id AND status = \'pending\'
|
||||
RETURNING id'
|
||||
);
|
||||
$stmt->execute(['id' => $id, 'response_by' => $responseBy]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function cancel(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE appointment_requests SET status = \'cancelled\' WHERE id = :id RETURNING id'
|
||||
);
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class InstructorAvailabilityRepository extends BaseRepository
|
||||
{
|
||||
public function getForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM instructor_availability
|
||||
WHERE instructor_id = :instructor_id
|
||||
ORDER BY weekday, time_from'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getActiveForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM instructor_availability
|
||||
WHERE instructor_id = :instructor_id AND is_active = 1
|
||||
ORDER BY weekday, time_from'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function upsert(int $instructorId, array $entries): void
|
||||
{
|
||||
// Delete all existing, then insert new (full replace)
|
||||
$this->db->beginTransaction();
|
||||
try {
|
||||
$stmt = $this->db->prepare('DELETE FROM instructor_availability WHERE instructor_id = :instructor_id');
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
|
||||
$insert = $this->db->prepare(
|
||||
'INSERT INTO instructor_availability (instructor_id, weekday, time_from, time_to, is_active)
|
||||
VALUES (:instructor_id, :weekday, :time_from, :time_to, :is_active)'
|
||||
);
|
||||
foreach ($entries as $e) {
|
||||
$insert->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'weekday' => (int) $e['weekday'],
|
||||
'time_from' => $e['time_from'],
|
||||
'time_to' => $e['time_to'],
|
||||
'is_active' => (int) ($e['is_active'] ?? 1),
|
||||
]);
|
||||
}
|
||||
$this->db->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare('DELETE FROM instructor_availability WHERE id = :id RETURNING id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class InstructorBreakRuleRepository extends BaseRepository
|
||||
{
|
||||
public function getForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM instructor_break_rules
|
||||
WHERE instructor_id = :instructor_id
|
||||
ORDER BY condition_type, threshold'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getActiveForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM instructor_break_rules
|
||||
WHERE instructor_id = :instructor_id AND is_active = 1
|
||||
ORDER BY condition_type, threshold'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function create(
|
||||
int $instructorId,
|
||||
string $conditionType,
|
||||
string $operator,
|
||||
float $threshold,
|
||||
int $breakMinutes,
|
||||
bool $isActive = true
|
||||
): array {
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO instructor_break_rules
|
||||
(instructor_id, condition_type, operator, threshold, break_minutes, is_active)
|
||||
VALUES (:instructor_id, :condition_type, :operator, :threshold, :break_minutes, :is_active)
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'condition_type' => $conditionType,
|
||||
'operator' => $operator,
|
||||
'threshold' => $threshold,
|
||||
'break_minutes' => $breakMinutes,
|
||||
'is_active' => $isActive ? 1 : 0,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function update(
|
||||
int $id,
|
||||
string $conditionType,
|
||||
string $operator,
|
||||
float $threshold,
|
||||
int $breakMinutes,
|
||||
bool $isActive
|
||||
): bool {
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE instructor_break_rules
|
||||
SET condition_type = :condition_type, operator = :operator,
|
||||
threshold = :threshold, break_minutes = :break_minutes, is_active = :is_active
|
||||
WHERE id = :id RETURNING id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'id' => $id,
|
||||
'condition_type' => $conditionType,
|
||||
'operator' => $operator,
|
||||
'threshold' => $threshold,
|
||||
'break_minutes' => $breakMinutes,
|
||||
'is_active' => $isActive ? 1 : 0,
|
||||
]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare('DELETE FROM instructor_break_rules WHERE id = :id RETURNING id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function setActive(int $id, bool $isActive): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE instructor_break_rules SET is_active = :is_active WHERE id = :id RETURNING id'
|
||||
);
|
||||
$stmt->execute(['id' => $id, 'is_active' => $isActive ? 1 : 0]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class InstructorHomeToTenantRepository extends BaseRepository
|
||||
{
|
||||
public function getForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT h.*, t.name AS tenant_name
|
||||
FROM instructor_home_to_tenant h
|
||||
JOIN tenants t ON t.id = h.tenant_id
|
||||
WHERE h.instructor_id = :instructor_id
|
||||
ORDER BY t.name'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getMinutes(int $instructorId, int $tenantId): ?int
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT minutes FROM instructor_home_to_tenant
|
||||
WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]);
|
||||
$result = $stmt->fetch();
|
||||
return $result ? (int) $result['minutes'] : null;
|
||||
}
|
||||
|
||||
public function upsert(int $instructorId, int $tenantId, int $minutes): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO instructor_home_to_tenant (instructor_id, tenant_id, minutes)
|
||||
VALUES (:instructor_id, :tenant_id, :minutes)
|
||||
ON CONFLICT (instructor_id, tenant_id)
|
||||
DO UPDATE SET minutes = :minutes
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'tenant_id' => $tenantId,
|
||||
'minutes' => $minutes,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare('DELETE FROM instructor_home_to_tenant WHERE id = :id RETURNING id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
}
|
||||
@@ -79,4 +79,41 @@ final class InstructorRepository extends BaseRepository
|
||||
|
||||
return $statement->fetch() ?: null;
|
||||
}
|
||||
|
||||
public function findById(int $id): ?array
|
||||
{
|
||||
$statement = $this->db->prepare('SELECT * FROM instructors WHERE id = :id');
|
||||
$statement->execute(['id' => $id]);
|
||||
|
||||
return $statement->fetch() ?: null;
|
||||
}
|
||||
|
||||
public function listForTenant(int $tenantId): array
|
||||
{
|
||||
$statement = $this->db->prepare('SELECT * FROM instructors WHERE tenant_id = :tenant_id AND is_active = 1 ORDER BY last_name, first_name');
|
||||
$statement->execute(['tenant_id' => $tenantId]);
|
||||
|
||||
return $statement->fetchAll();
|
||||
}
|
||||
|
||||
public function updateSettings(int $instructorId, array $settings): bool
|
||||
{
|
||||
$allowed = ['max_daily_units', 'max_daily_override', 'max_weekly_units', 'max_weekly_override', 'booking_enabled'];
|
||||
$sets = [];
|
||||
$params = ['id' => $instructorId];
|
||||
foreach ($allowed as $key) {
|
||||
if (array_key_exists($key, $settings)) {
|
||||
$sets[] = "{$key} = :{$key}";
|
||||
$params[$key] = $settings[$key];
|
||||
}
|
||||
}
|
||||
if (empty($sets)) {
|
||||
return false;
|
||||
}
|
||||
$sql = 'UPDATE instructors SET ' . implode(', ', $sets) . ', updated_at = :updated_at WHERE id = :id';
|
||||
$params['updated_at'] = $this->now();
|
||||
$statement = $this->db->prepare($sql);
|
||||
$statement->execute($params);
|
||||
return (bool) $statement->rowCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class InstructorTenantRepository extends BaseRepository
|
||||
{
|
||||
public function getTenantsForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT it.*, t.name AS tenant_name
|
||||
FROM instructor_tenants it
|
||||
JOIN tenants t ON t.id = it.tenant_id
|
||||
WHERE it.instructor_id = :instructor_id
|
||||
ORDER BY it.is_primary DESC, t.name'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function isPrimary(int $instructorId, int $tenantId): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT 1 FROM instructor_tenants WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id AND is_primary = 1'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function add(int $instructorId, int $tenantId, bool $isPrimary = false): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO instructor_tenants (instructor_id, tenant_id, is_primary)
|
||||
VALUES (:instructor_id, :tenant_id, :is_primary)
|
||||
ON CONFLICT (instructor_id, tenant_id) DO UPDATE SET is_primary = :is_primary
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'tenant_id' => $tenantId,
|
||||
'is_primary' => $isPrimary ? 1 : 0,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function remove(int $instructorId, int $tenantId): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'DELETE FROM instructor_tenants WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id RETURNING id'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function setPrimary(int $instructorId, int $tenantId): void
|
||||
{
|
||||
$this->db->beginTransaction();
|
||||
try {
|
||||
$stmt = $this->db->prepare('UPDATE instructor_tenants SET is_primary = 0 WHERE instructor_id = :instructor_id');
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
$stmt = $this->db->prepare('UPDATE instructor_tenants SET is_primary = 1 WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id');
|
||||
$stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]);
|
||||
$this->db->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$this->db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class InstructorTravelTimeRepository extends BaseRepository
|
||||
{
|
||||
public function getForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT t.*,
|
||||
ft.name AS from_tenant_name,
|
||||
tt.name AS to_tenant_name
|
||||
FROM instructor_travel_times t
|
||||
JOIN tenants ft ON ft.id = t.from_tenant_id
|
||||
JOIN tenants tt ON tt.id = t.to_tenant_id
|
||||
WHERE t.instructor_id = :instructor_id
|
||||
ORDER BY ft.name, tt.name'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getMinutes(int $instructorId, int $fromTenantId, int $toTenantId): ?int
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT minutes FROM instructor_travel_times
|
||||
WHERE instructor_id = :instructor_id
|
||||
AND from_tenant_id = :from_tenant_id
|
||||
AND to_tenant_id = :to_tenant_id'
|
||||
);
|
||||
$stmt->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'from_tenant_id' => $fromTenantId,
|
||||
'to_tenant_id' => $toTenantId,
|
||||
]);
|
||||
$result = $stmt->fetch();
|
||||
return $result ? (int) $result['minutes'] : null;
|
||||
}
|
||||
|
||||
public function upsert(int $instructorId, int $fromTenantId, int $toTenantId, int $minutes): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO instructor_travel_times (instructor_id, from_tenant_id, to_tenant_id, minutes)
|
||||
VALUES (:instructor_id, :from_tenant_id, :to_tenant_id, :minutes)
|
||||
ON CONFLICT (instructor_id, from_tenant_id, to_tenant_id)
|
||||
DO UPDATE SET minutes = :minutes
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'from_tenant_id' => $fromTenantId,
|
||||
'to_tenant_id' => $toTenantId,
|
||||
'minutes' => $minutes,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare('DELETE FROM instructor_travel_times WHERE id = :id RETURNING id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class InstructorVacationRepository extends BaseRepository
|
||||
{
|
||||
public function getForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM instructor_vacations
|
||||
WHERE instructor_id = :instructor_id
|
||||
ORDER BY date_from ASC'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getForInstructorInRange(int $instructorId, string $from, string $to): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM instructor_vacations
|
||||
WHERE instructor_id = :instructor_id
|
||||
AND date_from <= :to AND date_to >= :from
|
||||
ORDER BY date_from ASC'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId, 'from' => $from, 'to' => $to]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function create(int $instructorId, string $dateFrom, string $dateTo, string $notes = ''): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO instructor_vacations (instructor_id, date_from, date_to, notes)
|
||||
VALUES (:instructor_id, :date_from, :date_to, :notes)
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'date_from' => $dateFrom,
|
||||
'date_to' => $dateTo,
|
||||
'notes' => $notes,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare('DELETE FROM instructor_vacations WHERE id = :id RETURNING id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class PrivateAppointmentRepository extends BaseRepository
|
||||
{
|
||||
public function getForInstructor(int $instructorId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM private_appointments
|
||||
WHERE instructor_id = :instructor_id
|
||||
ORDER BY date ASC, time_from ASC'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getForInstructorInRange(int $instructorId, string $from, string $to): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM private_appointments
|
||||
WHERE instructor_id = :instructor_id
|
||||
AND date >= :from AND date <= :to
|
||||
ORDER BY date ASC, time_from ASC'
|
||||
);
|
||||
$stmt->execute(['instructor_id' => $instructorId, 'from' => $from, 'to' => $to]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function create(int $instructorId, string $title, string $date, string $timeFrom, string $timeTo, string $color = '#9ca3af'): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO private_appointments (instructor_id, title, date, time_from, time_to, color)
|
||||
VALUES (:instructor_id, :title, :date, :time_from, :time_to, :color)
|
||||
RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'instructor_id' => $instructorId,
|
||||
'title' => $title,
|
||||
'date' => $date,
|
||||
'time_from' => $timeFrom,
|
||||
'time_to' => $timeTo,
|
||||
'color' => $color,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare('DELETE FROM private_appointments WHERE id = :id RETURNING id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class StudentTenantRepository extends BaseRepository
|
||||
{
|
||||
public function findByUserAndTenant(int $userId, int $tenantId): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM student_tenants WHERE student_user_id = :user_id AND tenant_id = :tenant_id'
|
||||
);
|
||||
$stmt->execute(['user_id' => $userId, 'tenant_id' => $tenantId]);
|
||||
$result = $stmt->fetch();
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
public function create(int $userId, int $tenantId, string $status = 'pending'): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO student_tenants (student_user_id, tenant_id, status)
|
||||
VALUES (:user_id, :tenant_id, :status) RETURNING *'
|
||||
);
|
||||
$stmt->execute([
|
||||
'user_id' => $userId,
|
||||
'tenant_id' => $tenantId,
|
||||
'status' => $status,
|
||||
]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function confirm(int $id, int $confirmedBy): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE student_tenants
|
||||
SET status = \'active\', confirmed_at = NOW(), confirmed_by = :confirmed_by
|
||||
WHERE id = :id RETURNING id'
|
||||
);
|
||||
$stmt->execute(['id' => $id, 'confirmed_by' => $confirmedBy]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function suspend(int $id): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE student_tenants SET status = \'suspended\' WHERE id = :id RETURNING id'
|
||||
);
|
||||
$stmt->execute(['id' => $id]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function getPendingForTenant(int $tenantId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT st.*, u.email, u.first_name, u.last_name
|
||||
FROM student_tenants st
|
||||
JOIN users u ON u.id = st.student_user_id
|
||||
WHERE st.tenant_id = :tenant_id AND st.status = \'pending\'
|
||||
ORDER BY st.registered_at DESC'
|
||||
);
|
||||
$stmt->execute(['tenant_id' => $tenantId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function getActiveForUser(int $userId): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT st.*, t.name AS tenant_name
|
||||
FROM student_tenants st
|
||||
JOIN tenants t ON t.id = st.tenant_id
|
||||
WHERE st.student_user_id = :user_id AND st.status = \'active\''
|
||||
);
|
||||
$stmt->execute(['user_id' => $userId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
final class TenantRegistrationCodeRepository extends BaseRepository
|
||||
{
|
||||
public function findByCode(string $code): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT trc.*, t.name AS tenant_name
|
||||
FROM tenant_registration_codes trc
|
||||
JOIN tenants t ON t.id = trc.tenant_id
|
||||
WHERE trc.code = :code AND trc.is_active = 1'
|
||||
);
|
||||
$stmt->execute(['code' => $code]);
|
||||
$result = $stmt->fetch();
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
public function findByTenant(int $tenantId): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'SELECT * FROM tenant_registration_codes WHERE tenant_id = :tenant_id AND is_active = 1'
|
||||
);
|
||||
$stmt->execute(['tenant_id' => $tenantId]);
|
||||
$result = $stmt->fetch();
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
public function create(int $tenantId, string $code): array
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'INSERT INTO tenant_registration_codes (tenant_id, code) VALUES (:tenant_id, :code) RETURNING *'
|
||||
);
|
||||
$stmt->execute(['tenant_id' => $tenantId, 'code' => $code]);
|
||||
return $stmt->fetch();
|
||||
}
|
||||
|
||||
public function update(int $tenantId, string $code): bool
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE tenant_registration_codes SET code = :code WHERE tenant_id = :tenant_id RETURNING id'
|
||||
);
|
||||
$stmt->execute(['tenant_id' => $tenantId, 'code' => $code]);
|
||||
return (bool) $stmt->fetch();
|
||||
}
|
||||
|
||||
public function incrementUsedCount(int $tenantId): void
|
||||
{
|
||||
$stmt = $this->db->prepare(
|
||||
'UPDATE tenant_registration_codes SET used_count = used_count + 1 WHERE tenant_id = :tenant_id'
|
||||
);
|
||||
$stmt->execute(['tenant_id' => $tenantId]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user