Commit 81622d8b authored by Mahmoud Aglan's avatar Mahmoud Aglan

fixed

parent abefb320
......@@ -208,7 +208,7 @@ class AuthController extends Controller
private function resolveHomePage(object $employee): string
{
$permissions = $employee->getPermissions();
$permissions = $employee->getAllPermissions();
$menuItems = MenuRegistry::getVisible($permissions);
foreach ($menuItems as $item) {
......
......@@ -6,7 +6,13 @@ namespace App\Modules\SportsActivity\Controllers\Api;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Modules\SportsActivity\Services\MirrorStateService;
use App\Modules\SportsActivity\Services\BookingService;
use App\Modules\SportsActivity\Services\ConflictDetectionService;
use App\Modules\SportsActivity\Services\ScheduleGeneratorService;
use App\Modules\SportsActivity\Services\SlotAvailabilityService;
use App\Modules\SportsActivity\SaConstants;
class MirrorApiController extends Controller
{
......@@ -21,4 +27,617 @@ class MirrorApiController extends Controller
return $this->json($state);
}
public function groups(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$facility = $db->selectOne("SELECT discipline_id FROM sa_facilities WHERE id = ? AND is_archived = 0", [(int) $id]);
if (!$facility) {
return $this->json(['success' => false, 'error' => 'المرفق غير موجود']);
}
$groups = $db->select(
"SELECT g.id, g.code, g.name_ar, g.current_count, g.status,
p.name_ar as program_name, p.max_capacity, p.sessions_per_week,
p.session_duration_minutes,
c.full_name_ar as coach_name, c.id as coach_id,
(SELECT GROUP_CONCAT(CONCAT(gs.day_of_week, ':', gs.start_time, '-', gs.end_time) SEPARATOR '|')
FROM sa_group_schedule gs WHERE gs.group_id = g.id AND gs.is_active = 1) as schedule_info
FROM sa_groups g
JOIN sa_programs p ON p.id = g.program_id
LEFT JOIN sa_coaches c ON c.id = g.coach_id
WHERE p.discipline_id = ? AND g.is_archived = 0 AND g.status = 'active'
ORDER BY g.name_ar ASC",
[(int) $facility['discipline_id']]
);
return $this->json(['success' => true, 'groups' => $groups]);
}
public function quickBook(Request $request, string $id): Response
{
$body = $request->jsonBody();
$unitId = (int) ($body['unit_id'] ?? 0);
$date = trim((string) ($body['date'] ?? ''));
$startTime = trim((string) ($body['start_time'] ?? ''));
$endTime = trim((string) ($body['end_time'] ?? ''));
$bookingType = trim((string) ($body['booking_type'] ?? 'hourly'));
$groupId = (int) ($body['group_id'] ?? 0);
$coachId = (int) ($body['coach_id'] ?? 0);
$bookerName = trim((string) ($body['booker_name'] ?? ''));
$bookerType = trim((string) ($body['booker_type'] ?? 'guest'));
$notes = trim((string) ($body['notes'] ?? ''));
if ($unitId === 0 || $date === '' || $startTime === '' || $endTime === '') {
return $this->json(['success' => false, 'error' => 'بيانات ناقصة']);
}
if ($bookingType === 'training' && $groupId > 0) {
$result = BookingService::createTrainingBooking($groupId, $unitId, $date, $startTime, $endTime, $coachId ?: null);
return $this->json($result);
}
if ($bookingType === 'blocked' || $bookingType === 'maintenance') {
$db = App::getInstance()->db();
$employeeId = (int) (App::getInstance()->session()->get('employee_id') ?? 0);
$db->insert('sa_bookings', [
'booking_number' => 'BLK-' . date('ymdHis') . '-' . rand(100, 999),
'facility_unit_id' => $unitId,
'booking_type' => $bookingType,
'booking_date' => $date,
'start_time' => $startTime,
'end_time' => $endTime,
'spots_reserved' => 999,
'total_amount' => 0,
'payment_status' => 'unpaid',
'status' => 'confirmed',
'notes' => $notes ?: ($bookingType === 'maintenance' ? 'صيانة' : 'محجوز'),
'created_by' => $employeeId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
return $this->json(['success' => true, 'booking_id' => (int) $db->lastInsertId()]);
}
$result = BookingService::createHourlyBooking([
'facility_unit_id' => $unitId,
'booking_date' => $date,
'start_time' => $startTime,
'end_time' => $endTime,
'booker_type' => $bookerType,
'booker_name' => $bookerName,
'notes' => $notes,
]);
return $this->json($result);
}
public function quickSchedule(Request $request, string $id): Response
{
$body = $request->jsonBody();
$groupId = (int) ($body['group_id'] ?? 0);
$unitId = (int) ($body['unit_id'] ?? 0);
$dayOfWeek = isset($body['day_of_week']) ? (int) $body['day_of_week'] : -1;
$startTime = trim((string) ($body['start_time'] ?? ''));
$endTime = trim((string) ($body['end_time'] ?? ''));
$weeks = (int) ($body['weeks'] ?? 4);
if ($groupId === 0 || $unitId === 0 || $dayOfWeek < 0 || $startTime === '' || $endTime === '') {
return $this->json(['success' => false, 'error' => 'بيانات ناقصة']);
}
$db = App::getInstance()->db();
$existing = $db->selectOne(
"SELECT id FROM sa_group_schedule WHERE group_id = ? AND facility_unit_id = ? AND day_of_week = ? AND start_time = ? AND is_active = 1",
[$groupId, $unitId, $dayOfWeek, $startTime]
);
if (!$existing) {
$db->insert('sa_group_schedule', [
'group_id' => $groupId,
'facility_unit_id' => $unitId,
'day_of_week' => $dayOfWeek,
'start_time' => $startTime,
'end_time' => $endTime,
'is_active' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
$fromDate = date('Y-m-d');
$toDate = date('Y-m-d', strtotime("+{$weeks} weeks"));
$result = ScheduleGeneratorService::generateForGroup($groupId, $fromDate, $toDate);
return $this->json($result);
}
public function cancelBooking(Request $request, string $id): Response
{
$body = $request->jsonBody();
$bookingId = (int) ($body['booking_id'] ?? 0);
$reason = trim((string) ($body['reason'] ?? ''));
if ($bookingId === 0) {
return $this->json(['success' => false, 'error' => 'معرف الحجز مطلوب']);
}
$result = BookingService::cancel($bookingId, $reason);
return $this->json($result);
}
public function moveBooking(Request $request, string $id): Response
{
$body = $request->jsonBody();
$bookingId = (int) ($body['booking_id'] ?? 0);
$newUnitId = (int) ($body['new_unit_id'] ?? 0);
$newDate = trim((string) ($body['new_date'] ?? ''));
$newStartTime = trim((string) ($body['new_start_time'] ?? ''));
$newEndTime = trim((string) ($body['new_end_time'] ?? ''));
if ($bookingId === 0 || $newUnitId === 0 || $newDate === '' || $newStartTime === '' || $newEndTime === '') {
return $this->json(['success' => false, 'error' => 'بيانات ناقصة']);
}
$db = App::getInstance()->db();
$booking = $db->selectOne("SELECT * FROM sa_bookings WHERE id = ?", [$bookingId]);
if (!$booking || $booking['status'] === 'cancelled') {
return $this->json(['success' => false, 'error' => 'الحجز غير موجود أو ملغي']);
}
$spotsNeeded = (int) ($booking['spots_reserved'] ?: 1);
$availability = SlotAvailabilityService::check($newUnitId, $newDate, $newStartTime, $newEndTime, $spotsNeeded, $bookingId);
if (!$availability['available']) {
return $this->json(['success' => false, 'error' => $availability['reason']]);
}
if (!empty($booking['coach_id'])) {
$conflicts = ConflictDetectionService::check($newUnitId, $newDate, $newStartTime, $newEndTime, (int) $booking['coach_id'], $bookingId);
$blocking = array_filter($conflicts, fn($c) => $c['severity'] === 'blocking');
if (!empty($blocking)) {
return $this->json(['success' => false, 'error' => reset($blocking)['message']]);
}
}
$db->update('sa_bookings', [
'facility_unit_id' => $newUnitId,
'booking_date' => $newDate,
'start_time' => $newStartTime,
'end_time' => $newEndTime,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$bookingId]);
return $this->json(['success' => true]);
}
public function swapBookings(Request $request, string $id): Response
{
$body = $request->jsonBody();
$bookingIdA = (int) ($body['booking_id_a'] ?? 0);
$bookingIdB = (int) ($body['booking_id_b'] ?? 0);
if ($bookingIdA === 0 || $bookingIdB === 0) {
return $this->json(['success' => false, 'error' => 'يجب تحديد حجزين']);
}
$db = App::getInstance()->db();
$a = $db->selectOne("SELECT * FROM sa_bookings WHERE id = ? AND status != 'cancelled'", [$bookingIdA]);
$b = $db->selectOne("SELECT * FROM sa_bookings WHERE id = ? AND status != 'cancelled'", [$bookingIdB]);
if (!$a || !$b) {
return $this->json(['success' => false, 'error' => 'أحد الحجزين غير موجود']);
}
$db->beginTransaction();
try {
$db->update('sa_bookings', [
'facility_unit_id' => $b['facility_unit_id'],
'start_time' => $b['start_time'],
'end_time' => $b['end_time'],
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$bookingIdA]);
$db->update('sa_bookings', [
'facility_unit_id' => $a['facility_unit_id'],
'start_time' => $a['start_time'],
'end_time' => $a['end_time'],
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$bookingIdB]);
$db->commit();
} catch (\Throwable $e) {
$db->rollBack();
return $this->json(['success' => false, 'error' => 'فشل التبديل']);
}
return $this->json(['success' => true]);
}
public function copyDay(Request $request, string $id): Response
{
$body = $request->jsonBody();
$sourceDate = trim((string) ($body['source_date'] ?? ''));
$targetDate = trim((string) ($body['target_date'] ?? ''));
if ($sourceDate === '' || $targetDate === '' || $sourceDate === $targetDate) {
return $this->json(['success' => false, 'error' => 'يجب تحديد يوم مصدر ويوم هدف مختلفين']);
}
$db = App::getInstance()->db();
$employeeId = (int) (App::getInstance()->session()->get('employee_id') ?? 0);
$sourceBookings = $db->select(
"SELECT * FROM sa_bookings
WHERE facility_unit_id IN (SELECT id FROM sa_facility_units WHERE facility_id = ?)
AND booking_date = ? AND status != 'cancelled'",
[(int) $id, $sourceDate]
);
$copied = 0;
$skipped = 0;
foreach ($sourceBookings as $sb) {
$availability = SlotAvailabilityService::check(
(int) $sb['facility_unit_id'], $targetDate, $sb['start_time'], $sb['end_time'],
(int) ($sb['spots_reserved'] ?: 1)
);
if (!$availability['available']) {
$skipped++;
continue;
}
$db->insert('sa_bookings', [
'booking_number' => 'CPY-' . date('ymdHis') . '-' . rand(100, 999),
'facility_unit_id' => $sb['facility_unit_id'],
'booking_type' => $sb['booking_type'],
'booking_date' => $targetDate,
'start_time' => $sb['start_time'],
'end_time' => $sb['end_time'],
'group_id' => $sb['group_id'],
'coach_id' => $sb['coach_id'],
'booker_type' => $sb['booker_type'],
'booker_name' => $sb['booker_name'],
'participant_count' => $sb['participant_count'],
'spots_reserved' => $sb['spots_reserved'],
'total_amount' => 0,
'payment_status' => 'unpaid',
'status' => 'confirmed',
'notes' => 'نسخ من ' . $sourceDate,
'created_by' => $employeeId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
$copied++;
}
return $this->json(['success' => true, 'copied' => $copied, 'skipped' => $skipped]);
}
public function bulkBlock(Request $request, string $id): Response
{
$body = $request->jsonBody();
$cells = $body['cells'] ?? [];
$reason = trim((string) ($body['reason'] ?? 'محجوز'));
if (empty($cells) || !is_array($cells)) {
return $this->json(['success' => false, 'error' => 'يجب تحديد خلايا']);
}
$db = App::getInstance()->db();
$employeeId = (int) (App::getInstance()->session()->get('employee_id') ?? 0);
$blocked = 0;
foreach ($cells as $cell) {
$unitId = (int) ($cell['unit_id'] ?? 0);
$date = $cell['date'] ?? '';
$startTime = $cell['start_time'] ?? '';
$endTime = $cell['end_time'] ?? '';
if ($unitId === 0 || $date === '' || $startTime === '' || $endTime === '') {
continue;
}
$db->insert('sa_bookings', [
'booking_number' => 'BLK-' . date('ymdHis') . '-' . rand(100, 999),
'facility_unit_id' => $unitId,
'booking_type' => 'blocked',
'booking_date' => $date,
'start_time' => $startTime,
'end_time' => $endTime,
'spots_reserved' => 999,
'total_amount' => 0,
'payment_status' => 'unpaid',
'status' => 'confirmed',
'notes' => $reason,
'created_by' => $employeeId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
$blocked++;
}
return $this->json(['success' => true, 'blocked' => $blocked]);
}
public function extendBooking(Request $request, string $id): Response
{
$body = $request->jsonBody();
$bookingId = (int) ($body['booking_id'] ?? 0);
$newEndTime = trim((string) ($body['new_end_time'] ?? ''));
if ($bookingId === 0 || $newEndTime === '') {
return $this->json(['success' => false, 'error' => 'بيانات ناقصة']);
}
$db = App::getInstance()->db();
$booking = $db->selectOne("SELECT * FROM sa_bookings WHERE id = ? AND status != 'cancelled'", [$bookingId]);
if (!$booking) {
return $this->json(['success' => false, 'error' => 'الحجز غير موجود']);
}
$newStartTime = $booking['start_time'];
if (strtotime($newEndTime) <= strtotime($newStartTime)) {
return $this->json(['success' => false, 'error' => 'وقت الانتهاء يجب أن يكون بعد البداية']);
}
$availability = SlotAvailabilityService::check(
(int) $booking['facility_unit_id'], $booking['booking_date'],
$newStartTime, $newEndTime,
(int) ($booking['spots_reserved'] ?: 1), $bookingId
);
if (!$availability['available']) {
return $this->json(['success' => false, 'error' => $availability['reason']]);
}
$db->update('sa_bookings', [
'end_time' => $newEndTime,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$bookingId]);
return $this->json(['success' => true]);
}
public function coachesAvailable(Request $request, string $id): Response
{
$date = $request->get('date', date('Y-m-d'));
$startTime = $request->get('start', '');
$endTime = $request->get('end', '');
$db = App::getInstance()->db();
$facility = $db->selectOne("SELECT discipline_id FROM sa_facilities WHERE id = ? AND is_archived = 0", [(int) $id]);
if (!$facility) {
return $this->json(['success' => false, 'error' => 'المرفق غير موجود']);
}
$allCoaches = $db->select(
"SELECT c.id, c.full_name_ar as name_ar
FROM sa_coaches c
INNER JOIN sa_coach_disciplines cd ON cd.coach_id = c.id AND cd.discipline_id = ?
WHERE c.is_archived = 0 AND c.is_active = 1
ORDER BY c.full_name_ar",
[(int) $facility['discipline_id']]
);
if ($startTime === '' || $endTime === '') {
return $this->json(['success' => true, 'coaches' => $allCoaches]);
}
$available = [];
foreach ($allCoaches as $coach) {
$conflict = ConflictDetectionService::checkCoachConflict((int) $coach['id'], $date, $startTime, $endTime);
if (!$conflict) {
$available[] = $coach;
}
}
return $this->json(['success' => true, 'coaches' => $available]);
}
public function stats(Request $request, string $id): Response
{
$date = $request->get('date', date('Y-m-d'));
$db = App::getInstance()->db();
$revenueRow = $db->selectOne(
"SELECT COALESCE(SUM(total_amount), 0) as revenue, COUNT(*) as booking_count
FROM sa_bookings
WHERE facility_unit_id IN (SELECT id FROM sa_facility_units WHERE facility_id = ?)
AND booking_date = ? AND status != 'cancelled' AND booking_type != 'blocked'",
[(int) $id, $date]
);
$facility = $db->selectOne("SELECT operating_hours_json FROM sa_facilities WHERE id = ?", [(int) $id]);
$hours = json_decode($facility['operating_hours_json'] ?? '{}', true) ?: ['start' => '06:00', 'end' => '22:00', 'slot_minutes' => 60];
$slotMinutes = (int) $hours['slot_minutes'];
$totalMinutes = (strtotime($hours['end']) - strtotime($hours['start'])) / 60;
$totalSlots = (int) ($totalMinutes / $slotMinutes);
$unitCount = (int) $db->selectOne(
"SELECT COUNT(*) as cnt FROM sa_facility_units WHERE facility_id = ? AND is_active = 1",
[(int) $id]
)['cnt'];
$totalCapacity = $totalSlots * $unitCount;
$bookedSlots = $db->selectOne(
"SELECT COUNT(*) as cnt FROM sa_bookings
WHERE facility_unit_id IN (SELECT id FROM sa_facility_units WHERE facility_id = ?)
AND booking_date = ? AND status != 'cancelled'",
[(int) $id, $date]
);
$utilization = $totalCapacity > 0
? round(((int) $bookedSlots['cnt'] / $totalCapacity) * 100, 1)
: 0;
return $this->json([
'success' => true,
'revenue' => (float) $revenueRow['revenue'],
'booking_count' => (int) $revenueRow['booking_count'],
'utilization' => $utilization,
'total_slots' => $totalCapacity,
'booked_slots' => (int) $bookedSlots['cnt'],
]);
}
public function saveTemplate(Request $request, string $id): Response
{
$body = $request->jsonBody();
$name = trim((string) ($body['name'] ?? ''));
$description = trim((string) ($body['description'] ?? ''));
$date = trim((string) ($body['date'] ?? date('Y-m-d')));
if ($name === '') {
return $this->json(['success' => false, 'error' => 'اسم القالب مطلوب']);
}
$db = App::getInstance()->db();
$employeeId = (int) (App::getInstance()->session()->get('employee_id') ?? 0);
$weekStart = date('Y-m-d', strtotime('last saturday', strtotime($date . ' +1 day')));
$weekEnd = date('Y-m-d', strtotime($weekStart . ' +6 days'));
$bookings = $db->select(
"SELECT facility_unit_id as unit_id, DAYOFWEEK(booking_date) - 1 as day_of_week,
start_time, end_time, booking_type, group_id, coach_id, booker_type, notes
FROM sa_bookings
WHERE facility_unit_id IN (SELECT id FROM sa_facility_units WHERE facility_id = ?)
AND booking_date BETWEEN ? AND ? AND status != 'cancelled'
ORDER BY booking_date, start_time",
[(int) $id, $weekStart, $weekEnd]
);
$templateData = array_map(fn($b) => [
'unit_id' => (int) $b['unit_id'],
'day_of_week' => (int) $b['day_of_week'],
'start_time' => $b['start_time'],
'end_time' => $b['end_time'],
'booking_type' => $b['booking_type'],
'group_id' => $b['group_id'] ? (int) $b['group_id'] : null,
'coach_id' => $b['coach_id'] ? (int) $b['coach_id'] : null,
], $bookings);
$db->insert('sa_mirror_templates', [
'facility_id' => (int) $id,
'name' => $name,
'description' => $description ?: null,
'template_data' => json_encode($templateData),
'created_by' => $employeeId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
return $this->json(['success' => true, 'template_id' => (int) $db->lastInsertId(), 'entries' => count($templateData)]);
}
public function applyTemplate(Request $request, string $id): Response
{
$body = $request->jsonBody();
$templateId = (int) ($body['template_id'] ?? 0);
$targetWeekStart = trim((string) ($body['week_start'] ?? ''));
if ($templateId === 0 || $targetWeekStart === '') {
return $this->json(['success' => false, 'error' => 'بيانات ناقصة']);
}
$db = App::getInstance()->db();
$template = $db->selectOne("SELECT * FROM sa_mirror_templates WHERE id = ? AND facility_id = ?", [$templateId, (int) $id]);
if (!$template) {
return $this->json(['success' => false, 'error' => 'القالب غير موجود']);
}
$entries = json_decode($template['template_data'], true) ?: [];
$employeeId = (int) (App::getInstance()->session()->get('employee_id') ?? 0);
$applied = 0;
$skipped = 0;
foreach ($entries as $entry) {
$targetDate = date('Y-m-d', strtotime($targetWeekStart . ' +' . $entry['day_of_week'] . ' days'));
$availability = SlotAvailabilityService::check(
(int) $entry['unit_id'], $targetDate, $entry['start_time'], $entry['end_time']
);
if (!$availability['available']) {
$skipped++;
continue;
}
if ($entry['booking_type'] === 'training' && !empty($entry['group_id'])) {
$result = BookingService::createTrainingBooking(
(int) $entry['group_id'], (int) $entry['unit_id'],
$targetDate, $entry['start_time'], $entry['end_time'],
$entry['coach_id'] ? (int) $entry['coach_id'] : null
);
if ($result['success']) {
$applied++;
} else {
$skipped++;
}
} else {
$db->insert('sa_bookings', [
'booking_number' => 'TPL-' . date('ymdHis') . '-' . rand(100, 999),
'facility_unit_id' => (int) $entry['unit_id'],
'booking_type' => $entry['booking_type'],
'booking_date' => $targetDate,
'start_time' => $entry['start_time'],
'end_time' => $entry['end_time'],
'group_id' => $entry['group_id'] ?? null,
'coach_id' => $entry['coach_id'] ?? null,
'spots_reserved' => 1,
'total_amount' => 0,
'payment_status' => 'unpaid',
'status' => 'confirmed',
'notes' => 'تطبيق قالب: ' . $template['name'],
'created_by' => $employeeId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
$applied++;
}
}
return $this->json(['success' => true, 'applied' => $applied, 'skipped' => $skipped]);
}
public function templates(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$templates = $db->select(
"SELECT id, name, description, created_at FROM sa_mirror_templates WHERE facility_id = ? ORDER BY created_at DESC",
[(int) $id]
);
return $this->json(['success' => true, 'templates' => $templates]);
}
public function conflicts(Request $request, string $id): Response
{
$unitId = (int) $request->get('unit_id', 0);
$date = $request->get('date', '');
$startTime = $request->get('start_time', '');
$endTime = $request->get('end_time', '');
$coachId = (int) $request->get('coach_id', 0);
$excludeId = (int) $request->get('exclude_id', 0);
if ($unitId === 0 || $date === '' || $startTime === '' || $endTime === '') {
return $this->json(['success' => false, 'error' => 'بيانات ناقصة']);
}
$conflicts = ConflictDetectionService::check($unitId, $date, $startTime, $endTime, $coachId ?: null, $excludeId ?: null);
return $this->json(['success' => true, 'conflicts' => $conflicts]);
}
}
......@@ -190,6 +190,21 @@ return [
['GET', '/api/sa/coaches/search', 'SportsActivity\Controllers\Api\SmartFilterApiController@coaches', ['auth'], 'sa.group.view'],
['GET', '/api/sa/disciplines/list', 'SportsActivity\Controllers\Api\SmartFilterApiController@disciplines', ['auth'], 'sa.group.view'],
['GET', '/api/sa/mirror/{id:\d+}/state', 'SportsActivity\Controllers\Api\MirrorApiController@state', ['auth'], 'sa.mirror.view'],
['GET', '/api/sa/mirror/{id:\d+}/groups', 'SportsActivity\Controllers\Api\MirrorApiController@groups', ['auth'], 'sa.mirror.view'],
['GET', '/api/sa/mirror/{id:\d+}/coaches-available', 'SportsActivity\Controllers\Api\MirrorApiController@coachesAvailable', ['auth'], 'sa.mirror.view'],
['GET', '/api/sa/mirror/{id:\d+}/stats', 'SportsActivity\Controllers\Api\MirrorApiController@stats', ['auth'], 'sa.mirror.view'],
['GET', '/api/sa/mirror/{id:\d+}/templates', 'SportsActivity\Controllers\Api\MirrorApiController@templates', ['auth'], 'sa.mirror.view'],
['GET', '/api/sa/mirror/{id:\d+}/conflicts', 'SportsActivity\Controllers\Api\MirrorApiController@conflicts', ['auth'], 'sa.mirror.view'],
['POST', '/api/sa/mirror/{id:\d+}/quick-book', 'SportsActivity\Controllers\Api\MirrorApiController@quickBook', ['auth', 'csrf'], 'sa.booking.manage'],
['POST', '/api/sa/mirror/{id:\d+}/quick-schedule', 'SportsActivity\Controllers\Api\MirrorApiController@quickSchedule', ['auth', 'csrf'], 'sa.schedule.manage'],
['POST', '/api/sa/mirror/{id:\d+}/cancel-booking', 'SportsActivity\Controllers\Api\MirrorApiController@cancelBooking', ['auth', 'csrf'], 'sa.booking.manage'],
['POST', '/api/sa/mirror/{id:\d+}/move-booking', 'SportsActivity\Controllers\Api\MirrorApiController@moveBooking', ['auth', 'csrf'], 'sa.booking.manage'],
['POST', '/api/sa/mirror/{id:\d+}/swap-bookings', 'SportsActivity\Controllers\Api\MirrorApiController@swapBookings', ['auth', 'csrf'], 'sa.booking.manage'],
['POST', '/api/sa/mirror/{id:\d+}/copy-day', 'SportsActivity\Controllers\Api\MirrorApiController@copyDay', ['auth', 'csrf'], 'sa.booking.manage'],
['POST', '/api/sa/mirror/{id:\d+}/bulk-block', 'SportsActivity\Controllers\Api\MirrorApiController@bulkBlock', ['auth', 'csrf'], 'sa.booking.manage'],
['POST', '/api/sa/mirror/{id:\d+}/extend-booking', 'SportsActivity\Controllers\Api\MirrorApiController@extendBooking', ['auth', 'csrf'], 'sa.booking.manage'],
['POST', '/api/sa/mirror/{id:\d+}/save-template', 'SportsActivity\Controllers\Api\MirrorApiController@saveTemplate', ['auth', 'csrf'], 'sa.schedule.manage'],
['POST', '/api/sa/mirror/{id:\d+}/apply-template', 'SportsActivity\Controllers\Api\MirrorApiController@applyTemplate', ['auth', 'csrf'], 'sa.schedule.manage'],
['GET', '/api/sa/pool-grid/{id:\d+}/state', 'SportsActivity\Controllers\Api\PoolGridApiController@state', ['auth'], 'sa.pool-grid.manage'],
['POST', '/api/sa/pool-grid/{id:\d+}/assign', 'SportsActivity\Controllers\Api\PoolGridApiController@assign', ['auth', 'csrf'], 'sa.pool-grid.manage'],
['POST', '/api/sa/pool-grid/{id:\d+}/clear', 'SportsActivity\Controllers\Api\PoolGridApiController@clear', ['auth', 'csrf'], 'sa.pool-grid.manage'],
......
<?php
declare(strict_types=1);
return [
'up' => "
CREATE TABLE IF NOT EXISTS sa_mirror_templates (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
facility_id INT UNSIGNED NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT NULL,
template_data JSON NOT NULL COMMENT 'Array of {unit_id, day_of_week, start_time, end_time, booking_type, group_id}',
created_by BIGINT UNSIGNED NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_smt_facility (facility_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
'down' => "DROP TABLE IF EXISTS sa_mirror_templates",
];
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment