38960-vm/api/appointments.php
2026-03-16 10:31:04 +00:00

282 lines
11 KiB
PHP

<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../helpers.php';
header('Content-Type: application/json');
$db = db();
$lang = $_SESSION['lang'] ?? 'en';
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if ($method === 'GET') {
$id = $_GET['id'] ?? null;
if ($id) {
// Fetch single appointment
$stmt = $db->prepare("SELECT * FROM appointments WHERE id = ?");
$stmt->execute([$id]);
$appointment = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode($appointment);
exit;
}
$startStr = $_GET['start'] ?? null;
$endStr = $_GET['end'] ?? null;
$doctor_id = $_GET['doctor_id'] ?? null;
$events = [];
$businessHours = [];
// Fetch Appointments
$query = "
SELECT
a.id, a.start_time as start, a.end_time as end, a.reason, a.status,
a.patient_id, a.doctor_id,
p.name as patient_name,
d.name_$lang as doctor_name
FROM appointments a
JOIN patients p ON a.patient_id = p.id
JOIN doctors d ON a.doctor_id = d.id
WHERE 1=1";
$params = [];
if ($startStr) { $query .= " AND a.start_time >= ?"; $params[] = $startStr; }
if ($endStr) { $query .= " AND a.start_time <= ?"; $params[] = $endStr; }
if ($doctor_id) { $query .= " AND a.doctor_id = ?"; $params[] = $doctor_id; }
$stmt = $db->prepare($query);
$stmt->execute($params);
$appointments = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($appointments as $a) {
$color = '#0d6efd'; // blue
if ($a['status'] === 'Completed') $color = '#198754'; // green
if ($a['status'] === 'Cancelled') $color = '#dc3545'; // red
$events[] = [
'id' => $a['id'],
'title' => $a['patient_name'] . ' (' . $a['doctor_name'] . ')',
'start' => $a['start'],
'end' => $a['end'],
'color' => $color,
'extendedProps' => [
'type' => 'appointment',
'patient_id' => $a['patient_id'],
'doctor_id' => $a['doctor_id'],
'patient_name' => $a['patient_name'],
'doctor_name' => $a['doctor_name'],
'status' => $a['status'],
'reason' => $a['reason']
]
];
}
// Fetch Public Holidays
$holidayQuery = "SELECT holiday_date as start, name_$lang as title FROM holidays WHERE 1=1";
$holidayParams = [];
if ($startStr) { $holidayQuery .= " AND holiday_date >= ?"; $holidayParams[] = date('Y-m-d', strtotime($startStr)); }
if ($endStr) { $holidayQuery .= " AND holiday_date <= ?"; $holidayParams[] = date('Y-m-d', strtotime($endStr)); }
$stmt = $db->prepare($holidayQuery);
$stmt->execute($holidayParams);
$holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($holidays as $h) {
// Render daily blocks for time grid
$events[] = [
'id' => 'hol_bg_' . $h['start'],
'start' => $h['start'] . 'T00:00:00',
'end' => $h['start'] . 'T23:59:59',
'allDay' => false,
'display' => 'background',
'backgroundColor' => 'rgba(255, 193, 7, 0.5)',
'overlap' => false,
'extendedProps' => ['type' => 'public_holiday', 'blocks_selection' => true]
];
// Visible event strip across the top
$events[] = [
'id' => 'hol_title_' . $h['start'],
'title' => __('holiday') . ': ' . $h['title'],
'start' => $h['start'],
'end' => $h['start'],
'allDay' => true,
'color' => '#ffc107',
'textColor' => '#000',
'extendedProps' => ['type' => 'public_holiday']
];
}
// Fetch Doctor Holidays
$docHolidayQuery = "SELECT dh.*, d.name_$lang as doctor_name FROM doctor_holidays dh JOIN doctors d ON dh.doctor_id = d.id WHERE 1=1";
$docHolidayParams = [];
// Date filtering for doctor holidays (ranges)
if ($startStr && $endStr) {
$docHolidayQuery .= " AND dh.start_date <= ? AND dh.end_date >= ?";
$docHolidayParams[] = date('Y-m-d', strtotime($endStr));
$docHolidayParams[] = date('Y-m-d', strtotime($startStr));
}
if ($doctor_id) {
$docHolidayQuery .= " AND dh.doctor_id = ?";
$docHolidayParams[] = $doctor_id;
}
$stmt = $db->prepare($docHolidayQuery);
$stmt->execute($docHolidayParams);
$docHolidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($docHolidays as $dh) {
$title = $dh['doctor_name'] . ' - ' . ($dh['note'] ?: 'Holiday');
$endDayStr = date('Y-m-d', strtotime($dh['end_date'] . ' +1 day'));
$currentDate = strtotime($dh['start_date']);
$endDate = strtotime($dh['end_date']);
// Output background events for each day individually to ensure they render in the time grid perfectly
while ($currentDate <= $endDate) {
$dateStr = date('Y-m-d', $currentDate);
// To block selection, FullCalendar relies on selectOverlap checking properties.
// We'll set 'overlap: false' when filtered to a specific doctor.
// Also we give it time 00:00:00 to 24:00:00 to fill the column.
$isFilteredDoctor = ($doctor_id && $doctor_id == $dh['doctor_id']);
$events[] = [
'id' => 'doc_hol_bg_' . $dh['id'] . '_' . $dateStr,
'start' => $dateStr . 'T00:00:00',
'end' => $dateStr . 'T23:59:59',
'display' => 'background',
'allDay' => false,
'backgroundColor' => $isFilteredDoctor ? 'rgba(255, 193, 7, 0.5)' : 'rgba(255, 193, 7, 0.15)',
'overlap' => !$isFilteredDoctor,
'extendedProps' => ['type' => 'doctor_holiday', 'doctor_id' => $dh['doctor_id'], 'blocks_selection' => $isFilteredDoctor]
];
$currentDate = strtotime('+1 day', $currentDate);
}
// Visible event strip across the top (allDay)
$events[] = [
'id' => 'doc_hol_' . $dh['id'],
'title' => $title,
'start' => $dh['start_date'],
'end' => $endDayStr,
'allDay' => true,
'color' => '#ffc107',
'textColor' => '#000',
'extendedProps' => ['type' => 'doctor_holiday', 'doctor_id' => $dh['doctor_id']]
];
}
// Fetch Doctor Business Hours
if ($doctor_id) {
$scheduleStmt = $db->prepare("SELECT day_of_week as day, start_time as start, end_time as end FROM doctor_schedules WHERE doctor_id = ?");
$scheduleStmt->execute([$doctor_id]);
$schedules = $scheduleStmt->fetchAll(PDO::FETCH_ASSOC);
$bhMap = [];
foreach ($schedules as $s) {
$key = $s['start'] . '-' . $s['end'];
if (!isset($bhMap[$key])) {
$bhMap[$key] = [
'daysOfWeek' => [],
'startTime' => $s['start'],
'endTime' => $s['end']
];
}
$bhMap[$key]['daysOfWeek'][] = (int)$s['day'];
}
$businessHours = array_values($bhMap);
} else {
$s = get_system_settings();
$st = $s['working_hours_start'] ?? '08:00';
$et = $s['working_hours_end'] ?? '17:00';
$businessHours = [
[
'daysOfWeek' => [0, 1, 2, 3, 4, 5, 6],
'startTime' => $st,
'endTime' => $et
]
];
}
$s = get_system_settings();
$global_start = $s['working_hours_start'] ?? '08:00';
$global_end = $s['working_hours_end'] ?? '17:00';
echo json_encode([
'events' => $events,
'businessHours' => $businessHours,
'settings' => [
'working_hours_start' => $global_start,
'working_hours_end' => $global_end
]
]);
exit;
}
function checkDoctorHoliday($db, $doctor_id, $start_time) {
if (!$doctor_id || !$start_time) return false;
$date = date('Y-m-d', strtotime($start_time));
$stmt = $db->prepare("SELECT COUNT(*) FROM doctor_holidays WHERE doctor_id = ? AND ? BETWEEN start_date AND end_date");
$stmt->execute([$doctor_id, $date]);
return $stmt->fetchColumn() > 0;
}
if ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
$action = $input['action'] ?? '';
if ($action === 'create') {
$patient_id = $input['patient_id'] ?? '';
$doctor_id = $input['doctor_id'] ?? '';
$start_time = $input['start_time'] ?? '';
$reason = $input['reason'] ?? '';
if ($patient_id && $doctor_id && $start_time) {
// Check for holiday conflict
if (checkDoctorHoliday($db, $doctor_id, $start_time)) {
echo json_encode(['success' => false, 'error' => 'Doctor is on holiday on this date.']);
exit;
}
$stmt = $db->prepare("INSERT INTO appointments (patient_id, doctor_id, start_time, end_time, reason) VALUES (?, ?, ?, DATE_ADD(?, INTERVAL 30 MINUTE), ?)");
$stmt->execute([$patient_id, $doctor_id, $start_time, $start_time, $reason]);
echo json_encode(['success' => true, 'id' => $db->lastInsertId()]);
} else {
echo json_encode(['success' => false, 'error' => 'Missing fields']);
}
} elseif ($action === 'update') {
$id = $input['id'] ?? '';
$patient_id = $input['patient_id'] ?? '';
$doctor_id = $input['doctor_id'] ?? '';
$start_time = $input['start_time'] ?? '';
$status = $input['status'] ?? 'Scheduled';
$reason = $input['reason'] ?? '';
if ($id && $patient_id && $doctor_id && $start_time) {
// Check for holiday conflict
if (checkDoctorHoliday($db, $doctor_id, $start_time)) {
echo json_encode(['success' => false, 'error' => 'Doctor is on holiday on this date.']);
exit;
}
$stmt = $db->prepare("UPDATE appointments SET patient_id = ?, doctor_id = ?, start_time = ?, end_time = DATE_ADD(?, INTERVAL 30 MINUTE), status = ?, reason = ? WHERE id = ?");
$stmt->execute([$patient_id, $doctor_id, $start_time, $start_time, $status, $reason, $id]);
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Missing fields']);
}
} elseif ($action === 'delete') {
$id = $input['id'] ?? '';
if ($id) {
$stmt = $db->prepare("DELETE FROM appointments WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Missing ID']);
}
}
exit;
}