/home/techb158/workloadmatch.com/workloadmatch.com/BackUp/Manager
NameSizeModeActions
core/-0755rm
css/-0755rm
data/-0755rm
fonts/-0755rm
Inc/-0755rm
js/-0755rm
php/-0755rm
accesscontrol.php160390644editdlrm
AddClient.php39300644editdlrm
addcourse.php177360644editdlrm
addcoursesession.php205280644editdlrm
addcoursetime.php173050644editdlrm
addgroup - Copy.php212120644editdlrm
addgroup.php229990644editdlrm
addgroup1.php206670644editdlrm
addholiday.php180190644editdlrm
addmanager.php187650644editdlrm
addremediation.php178770644editdlrm
addretake.php219140644editdlrm
adduser.php300790644editdlrm
adduser1.php235270644editdlrm
Add_Course_Session.php65290644editdlrm
add_event.php19600644editdlrm
Add_Teacher_Unavailability.php17460644editdlrm
adminreports.php162190644editdlrm
AI_Generate_Schedule - Copy.php122170644editdlrm
AI_Generate_Schedule.php107310644editdlrm
ajax_course_group_Backup.php172700644editdlrm
ajax_course_group_Backup_1.php187420644editdlrm
ajax_fetch_schedule.php37600644editdlrm
ajax_get_group_slots.php8720644editdlrm
ajax_replace_saturday -Completed.php187390644editdlrm
ajax_replace_saturday - Copy (2).php187390644editdlrm
ajax_replace_saturday - Copy (3).php265540644editdlrm
ajax_replace_saturday - Copy (4).php266150644editdlrm
ajax_replace_saturday - Copy (5).php283740644editdlrm
ajax_replace_saturday - Copy.php134520644editdlrm
ajax_replace_saturday - Good.php172700644editdlrm
ajax_replace_saturday.php307790644editdlrm
ajax_swap_courses.php42790644editdlrm
assign_course_group-BackUp.php213310644editdlrm
assign_course_group - Copy (BackUp).php183150644editdlrm
assign_course_group - Copy.php213180644editdlrm
assign_course_group.php183150644editdlrm
assign_course_group1.php209620644editdlrm
Assign_Course_Report.php13750644editdlrm
assign_teacher_manual-1.php210950644editdlrm
assign_teacher_manual - Copy (2).php223280644editdlrm
assign_teacher_manual - Copy.php223880644editdlrm
assign_teacher_manual-Not work.php205850644editdlrm
assign_teacher_manual.php223280644editdlrm
assign_teacher_manual_force_conflicts.php223590644editdlrm
availableteacherreport.php137180644editdlrm
Available_Teacher_Report - Copy.php22360644editdlrm
Available_Teacher_Report.php76910644editdlrm
brokerreports.php163180644editdlrm
calendar - Copy (2).php168910644editdlrm
calendar - Copy.php231930644editdlrm
calendar.php306530644editdlrm
changestatus.php8650644editdlrm
Check_if_Teacher_Report.php15870644editdlrm
complete_schedule_report_by_teacher.php193120644editdlrm
courseflagtime.php34110644editdlrm
Create_Group_Schedule_Algorithm.php38280644editdlrm
Create_Schedule_Algorithm - Copy (2).php414120644editdlrm
Create_Schedule_Algorithm - Copy (3).php463520644editdlrm
Create_Schedule_Algorithm - Copy (4).php535690644editdlrm
Create_Schedule_Algorithm - Copy (5).php679560644editdlrm
Create_Schedule_Algorithm - Copy (6).php711340644editdlrm
Create_Schedule_Algorithm - Copy (7).php725290644editdlrm
Create_Schedule_Algorithm - Copy (8).php730840644editdlrm
Create_Schedule_Algorithm - Copy (9).php735770644editdlrm
Create_Schedule_Algorithm - Copy (10).php765050644editdlrm
Create_Schedule_Algorithm - Copy (11).php767640644editdlrm
Create_Schedule_Algorithm - Copy (12).php863240644editdlrm
Create_Schedule_Algorithm - Copy (13).php831330644editdlrm
Create_Schedule_Algorithm - Copy (14).php872740644editdlrm
Create_Schedule_Algorithm - Copy (get info only).php697470644editdlrm
Create_Schedule_Algorithm - Copy (with 2 course 2nd SPECIAL ).php823140644editdlrm
Create_Schedule_Algorithm - Copy.php405810644editdlrm
Create_Schedule_Algorithm.php882760644editdlrm
Create_Schedule_Algorithm_1.php414120644editdlrm
Create_Schedule_Algorithm_2.php765050644editdlrm
Create_Schedule_Algorithm_2_combined.php897640644editdlrm
Create_Schedule_Algorithm_2_combined_both_slots.php919320644editdlrm
Create_Schedule_Algorithm_2_fixed.php768290644editdlrm
Create_Schedule_Algorithm_3.php872740644editdlrm
Create_Schedule_Algorithm_4.php872740644editdlrm
dashboard-BackUp.php188060644editdlrm
dashboard.php225070644editdlrm
deleteschedulefromgroup.php168190644editdlrm
delete_course.php5800644editdlrm
delete_course_from_teacher.php49900644editdlrm
delete_course_from_teacher_backup.php30940644editdlrm
delete_event.php12120644editdlrm
delete_group.php18580644editdlrm
delete_holiday.php7150644editdlrm
delete_retake.php12760644editdlrm
delete_schedule_from_group-1.php18700644editdlrm
delete_schedule_from_group.php18500644editdlrm
delete_teacher_level.php7980644editdlrm
delete_teacher_unavailability.php13470644editdlrm
delete_timeslot.php5970644editdlrm
display_schedule-1.php188460644editdlrm
display_schedule.php188460644editdlrm
do_remove_saturday.php61700644editdlrm
edit_event.php16120644editdlrm
error_log740700644editdlrm
examschedulereport.php162670644editdlrm
Exam_Schedule_Report.php26770644editdlrm
export_absences.php12210644editdlrm
fetch_all_scheduled_courses.php63180644editdlrm
fetch_courses - BackUp.php97110644editdlrm
fetch_courses - Copy.php77190644editdlrm
fetch_courses.php91670644editdlrm
fetch_courses_for_program.php7880644editdlrm
fetch_courses_for_program_by_schedule - Copy.php22970644editdlrm
fetch_courses_for_program_by_schedule.php39350644editdlrm
fetch_courses_teacher_preference.php38520644editdlrm
fetch_course_for_added_group.php8970644editdlrm
fetch_course_hours.php6910644editdlrm
fetch_disabled_dates.php5180644editdlrm
fetch_events.php9750644editdlrm
fetch_exist_dates.php10990644editdlrm
fetch_groups_exist_in_schedule.php9560644editdlrm
fetch_groups_for_program.php8430644editdlrm
fetch_group_timeslot.php8470644editdlrm
fetch_reserve_courses.php74290644editdlrm
fetch_retake_courses.php9230644editdlrm
fetch_scheduled_courses.php21780644editdlrm
fetch_schedule_report_by_group.php54400644editdlrm
fetch_teachers_by_group.php44210644editdlrm
fetch_teachers_by_program.php18210644editdlrm
fetch_teachers_for_group.php31700644editdlrm
fetch_teacher_assignment.php247760644editdlrm
fetch_teacher_by_course.php38800644editdlrm
fetch_teacher_conflicts.php56820644editdlrm
fetch_teacher_conflicts_only.php76650644editdlrm
fetch_teacher_conflicts_only_force_conflicts.php71140644editdlrm
fetch_teacher_conflicts_showonly.php104470644editdlrm
fetch_teacher_load.php14820644editdlrm
fetch_teacher_prefrence_courses_accounting.php16060644editdlrm
find_replacement-1.php160750644editdlrm
find_replacement.php181610644editdlrm
followup.php161600644editdlrm
Generate Group Schedule.php169870644editdlrm
Generate Group Schedule 1.php181210644editdlrm
generateschedule.php157060644editdlrm
Generate_Schedule.php336200644editdlrm
generate_schedule_handler.php70950644editdlrm
Generate_Schedule_Script.php297310644editdlrm
getlang.php3260644editdlrm
getrefer.php43490644editdlrm
getstatus.php12600644editdlrm
get_groups_by_program.php6080644editdlrm
get_schedule_ai-1.php377550644editdlrm
get_schedule_ai - Copy.php364590644editdlrm
get_schedule_ai.php368550644editdlrm
get_schedule_report.php23930644editdlrm
group_schedule_form - Copy.php169940644editdlrm
group_schedule_form.php169940644editdlrm
main-header-inc.php11960644editdlrm
main-header.php166710644editdlrm
managecategory.php92330644editdlrm
managerreports.php250560644editdlrm
Preference_Accounting.php225030644editdlrm
profile.php206800644editdlrm
reference_list.php82070644editdlrm
remove_saturday_course.php195030644editdlrm
report_absence.php192870644editdlrm
report_absence_1.php61600644editdlrm
reserve_course.php189570644editdlrm
rewrite_ajax_replace_saturday.php128000644editdlrm
rewrite_Create_Schedule_Algorithm.php883180644editdlrm
rewrite_Create_Schedule_Algorithm_2.php889000644editdlrm
save_schedule.php29700644editdlrm
save_schedule_group_course.php253670644editdlrm
schedulereportbygroup.php177310644editdlrm
schedulereportbyteacher.php191110644editdlrm
scheduleteacherbygroupreport - Copy.php319860644editdlrm
scheduleteacherbygroupreport.php365560644editdlrm
scheduleteacherreport.php281500644editdlrm
schedule_report.php176640644editdlrm
Schedule_Report_by_Group.php36470644editdlrm
Schedule_Report_by_Teacher.php924330644editdlrm
selectuser.php11720644editdlrm
swapcourses.php194690644editdlrm
teacher_preference.php181820644editdlrm
teacher_preference_by_course.php192440644editdlrm
teacher_unavailability.php178350644editdlrm
test.php138950644editdlrm
test1.php218370644editdlrm
transfer_reference.php272880644editdlrm
unassigncoursereport.php160290644editdlrm
unassign_course.php190110644editdlrm
Unassign_Course_Report.php16970644editdlrm
unscheduledgroupreport.php156750644editdlrm
Unscheduled_Group_Report.php20070644editdlrm
updateclient.php147650644editdlrm
updatecourse.php216260644editdlrm
updatecoursesessionhours.php181970644editdlrm
updatecoursetime.php218310644editdlrm
updategroup - Copy.php245340644editdlrm
updategroup.php180010644editdlrm
updateholiday.php224930644editdlrm
updateteacherlevel.php245320644editdlrm
updateteacherunavailability.php141840644editdlrm
UpdateUser.php182140644editdlrm
Update_Course_Hours.php21350644editdlrm
Update_Follow_Up.php46180644editdlrm
update_reserve_course.php19340644editdlrm
update_teacher_level25080644editdlrm
update_teacher_level.php24300644editdlrm
useraccesslist.php128750644editdlrm
users.php90490644editdlrm
view_swap_log.php8940644editdlrm
Edit: /home/techb158/workloadmatch.com/workloadmatch.com/BackUp/Manager/Create_Schedule_Algorithm.php (88276B)
prepare("SELECT Program_ID, Program_Name FROM Programs"); $stmtGroup->execute(); $programData = $stmtGroup->get_result()->fetch_all(MYSQLI_ASSOC); $stmtGroup->close(); // ========================================= // 3. Define Utility Functions (Important Modularization) // ========================================= // --------------- Helper functions (these should be at the end of the file) --------------- /** * Find the best course to "push" for a blocked slot (retake), using the same logic as normal slot selection. */ function getCourseToPush( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ) { if ($specialCourseOnlyMode) return $specialCourseID; $candidates = array_filter($selectedCourses, function($cid) use ( $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap ) { if (($remainingSessions[$cid] ?? 0) <= 0) return false; if (isset($lockedSlot[$cid]) && $lockedSlot[$cid] !== $slotIndex) return false; if (isset($courseSlotMap[$cid]) && $courseSlotMap[$cid] !== $slotIndex) return false; return true; }); //################################################################## $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; return getNextCourseForSlot( $remainingSessions, $candidates, $usedToday, $courseCodes, $onlyCourseIDLeft ); //################################################################## /* $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; $courseID = getNextCourseForSlot( $remainingSessions, array_keys($activeCourses), $usedToday, $courseCodes, $onlyCourseIDLeft ); */ } /** * Find the next course to assign to a slot (normal scheduling). */ function selectNextCourse( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ) { if ($specialCourseOnlyMode) return $specialCourseID; $candidates = array_filter($selectedCourses, function($cid) use ( $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap ) { if (($remainingSessions[$cid] ?? 0) <= 0) return false; if (isset($lockedSlot[$cid]) && $lockedSlot[$cid] !== $slotIndex) return false; if (isset($courseSlotMap[$cid]) && $courseSlotMap[$cid] !== $slotIndex) return false; return true; }); $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; return getNextCourseForSlot( $remainingSessions, $candidates, $usedToday, $courseCodes, $onlyCourseIDLeft ); } /** * Locks a course to a slot if there are only 2 courses left and they're running in parallel. */ function applySlotLocking( $activeCourses, $remainingSessions, $specialCourseID, &$lockedSlot, $slotIndex ) { if (count($activeCourses) === 2) { foreach ($remainingSessions as $cid => $cnt) { if ($cnt > 0 && $cid !== $specialCourseID && !isset($lockedSlot[$cid])) { $lockedSlot[$cid] = $slotIndex; } } } } // ========================================= // 4. Handle POST Request to Generate Schedule // ========================================= // Format the label of the time slot, especially for Saturday sessions function format_time_slot_label($slot, $day) { if ($day === 'Saturday') { if (stripos($slot, 'evening') !== false) return 'Morning (Sat)'; if (stripos($slot, 'morning') !== false) return 'Morning'; } return $slot; } // Format the time range of the session based on the slot and the day function format_time_slot_range($slot, $start, $end, $day) { if ($day === 'Saturday') { if (stripos($slot, 'morning') !== false) return "08:30 AM - 12:30 PM"; if (stripos($slot, 'evening') !== false) return "09:00 AM - 01:00 PM"; } return date('h:i A', strtotime($start)) . ' - ' . date('h:i A', strtotime($end)); } // Get group configuration from database function get_group_info($mysqli, $Group_ID) { $stmt = $mysqli->prepare("SELECT Time_Slot, Time_From, Time_To, class_days, Weekend_Class, Group_Name FROM Manager_Group_Name WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); return $stmt->get_result()->fetch_assoc(); } // Retrieve time slots for the group function fetch_slot_times($mysqli, $slotLabels, $managerStart, $managerEnd) { $timeFrom = []; $timeTo = []; foreach ($slotLabels as $label) { $label = trim($label); $stmt = $mysqli->prepare("SELECT Time_From, Time_To FROM Time_Slot_Programs WHERE Time_Slot = ?"); $stmt->bind_param("s", $label); $stmt->execute(); $result = $stmt->get_result(); $validSlotFound = false; while ($row = $result->fetch_assoc()) { $slotStart = new DateTime($row['Time_From']); $slotEnd = new DateTime($row['Time_To']); if ($slotStart >= $managerStart && $slotEnd <= $managerEnd) { $timeFrom[] = $slotStart->format("H:i:s"); $timeTo[] = $slotEnd->format("H:i:s"); $validSlotFound = true; break; } } if (!$validSlotFound && count($slotLabels) === 1) { $timeFrom[] = $managerStart->format("H:i:s"); $timeTo[] = $managerEnd->format("H:i:s"); } } return [$timeFrom, $timeTo]; } // Collect all holiday dates in the current year /* function get_holidays($mysqli, $Start_Date) { $holidays = []; $res = $mysqli->query("SELECT Event_Title, Event_Start, Event_End FROM Events WHERE Calendar_Year = YEAR('$Start_Date')"); while ($row = $res->fetch_assoc()) { $start = new DateTime($row['Event_Start']); $end = new DateTime($row['Event_End']); while ($start <= $end) { $dateStr = $start->format('Y-m-d'); $holidays[$dateStr] = $row['Event_Title']; // 👈 include the title $start->modify('+1 day'); } } return $holidays; } */ function get_holidays($mysqli, $Start_Date, $End_Date = null) { $holidays = []; // If no end date is provided, default to one year after the start date if ($End_Date === null) { $End_Date = date('Y-m-d', strtotime('+1 year', strtotime($Start_Date))); } $stmt = $mysqli->prepare(" SELECT Event_Title, Event_Start, Event_End FROM Events WHERE Event_Start <= ? AND Event_End >= ? "); // All holidays that overlap the scheduling window [Start_Date, End_Date] $stmt->bind_param('ss', $End_Date, $Start_Date); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { $start = new DateTime($row['Event_Start']); $end = new DateTime($row['Event_End']); while ($start <= $end) { $dateStr = $start->format('Y-m-d'); $holidays[$dateStr] = $row['Event_Title']; $start->modify('+1 day'); } } return $holidays; } // Collect all retake dates for the group and program /*function get_retake_dates($mysqli, $Program_ID, $Group_ID) { $retakeDates = []; $stmt = $mysqli->prepare("SELECT Type, Retake_Date FROM Retake_Records WHERE Program_ID = ? AND Group_ID = ?"); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { $retakeDates[$row['Retake_Date']] = $row['Type']; // e.g., ['2025-09-10' => 'Midterm'] } return $retakeDates; }*/ // In get_retake_dates() - ensure consistent structure function get_retake_dates($mysqli, $Program_ID, $Group_ID) { $retakeDates = []; //$sql = "SELECT Group_Slot_ID, Time_Slot_Programs_ID, Time_Slot, Time_From, Time_To FROM Group_Slot_Mapping WHERE Group_ID = ?"; $sql = " SELECT rr.Time_Slot_Programs_ID, rr.Retake_Date, rr.Type, gsm.Group_Slot_ID, gsm.Time_Slot_Programs_ID, gsm.Time_Slot, gsm.Time_From, gsm.Time_To FROM Retake_Records AS rr INNER JOIN Group_Slot_Mapping AS gsm ON rr.Time_Slot_Programs_ID = gsm.Time_Slot_Programs_ID AND rr.Group_ID = gsm.Group_ID WHERE rr.Program_ID = ? AND rr.Group_ID = ? "; $stmt = $mysqli->prepare($sql); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); //echo "
Retake SQL result rows: " . $res->num_rows . "
"; while ($row = $res->fetch_assoc()) { $retakeDates[$row['Retake_Date']][$row['Group_Slot_ID']] = [ 'label' => $row['Time_Slot'], 'from' => $row['Time_From'], 'to' => $row['Time_To'], 'type' => $row['Type'] ]; } return $retakeDates; } function get_retake_dates_info($mysqli, $Program_ID, $Group_ID) { $retakeDates = []; $stmt = $mysqli->prepare(" SELECT Retake_Date, Type, Time_Slot_Programs_ID FROM Retake_Records WHERE Program_ID = ? AND Group_ID = ? "); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { // Store as [date][slotID] = type $retakeDates[$row['Retake_Date']][$row['Time_Slot_Programs_ID']] = $row['Type']; } return $retakeDates; /* $retakeDates = []; $sql = " SELECT rr.Retake_Date, rr.Type, gsm.Group_Slot_ID FROM Retake_Records AS rr INNER JOIN Group_Slot_Mapping AS gsm ON rr.Time_Slot_Programs_ID = gsm.Time_Slot_Programs_ID AND rr.Group_ID = gsm.Group_ID WHERE rr.Program_ID = ? AND rr.Group_ID = ? "; $stmt = $mysqli->prepare($sql); $stmt->bind_param("ii", $Program_ID, $Group_ID); $stmt->execute(); $res = $stmt->get_result(); while ($row = $res->fetch_assoc()) { // now keyed by [date][slotIndex] $date = $row['Retake_Date']; $slotIdx = intval($row['Group_Slot_ID']) - 1; // zero-based $retakeDates[$row['Retake_Date']][$row['Group_Slot_ID']] = $row['Type']; //$retakeDates[$date][$slotIdx] = $row['Type']; } return $retakeDates;*/ } // Calculate the average duration of one session function calculate_session_length($timeFrom, $timeTo) { $sessionLength = 0; foreach ($timeFrom as $i => $from) { $fromTime = new DateTime($from); $toTime = new DateTime($timeTo[$i]); $sessionLength += ($toTime->getTimestamp() - $fromTime->getTimestamp()) / 3600; } $slotCount = count($timeFrom); return $slotCount > 0 ? $sessionLength / $slotCount : 3; } // Calculate the number of sessions required for each course function calculate_course_sessions($mysqli, $Program_ID, $sessionLength) { $courseSessions = []; $res = $mysqli->query("SELECT Course_ID, Course_Time FROM Courses WHERE Program_ID = $Program_ID"); while ($row = $res->fetch_assoc()) { $courseID = $row['Course_ID']; $courseHours = $row['Course_Time']; $courseSessions[$courseID] = ceil($courseHours / $sessionLength); } return $courseSessions; } // Generate all valid class dates, excluding holidays, retakes, and July function generate_valid_dates($Start_Date, $End_Date, $classDays, $holidayDates, $retakeDates) { $validDates = []; $cur = new DateTime($Start_Date); $end = $End_Date ? new DateTime($End_Date) : null; while (!$end || $cur <= $end) { $day = $cur->format('l'); $dateStr = $cur->format('Y-m-d'); $month = (int) $cur->format('m'); // if ($month === 7 || !in_array($day, $classDays) || array_key_exists($dateStr, $holidayDates) || array_key_exists($dateStr, $retakeDates)) { // $cur->modify('+1 day'); // continue; //} if ($month === 7 || !in_array($day, $classDays) || array_key_exists($dateStr, $holidayDates)) { $cur->modify('+1 day'); continue; } $validDates[] = $dateStr; $cur->modify('+1 day'); if ($end === null && count($validDates) > 365) break; // Prevent infinite loop in fallback } return $validDates; } // Build calendar excluding holidays, retakes, and July function build_schedule_calendar($Start_Date, $End_Date, $classDays, $timeSlots, $mysqli, $Program_ID, $Group_ID) { $start = new DateTime($Start_Date); $end = $End_Date ? new DateTime($End_Date) : new DateTime('+1 year'); $calendar = []; $holidayDates = get_holidays($mysqli, $Start_Date); $retakeDates = get_retake_dates($mysqli, $Program_ID, $Group_ID); $cur = clone $start; while ($cur <= $end) { $day = $cur->format('l'); $dateStr = $cur->format('Y-m-d'); $month = (int) $cur->format('m'); if ($month !== 7 && in_array($day, $classDays) && !in_array($dateStr, $holidayDates) && !in_array($dateStr, $retakeDates)) { foreach ($timeSlots as $slot) { $calendar[$dateStr][trim($slot)] = 'free'; } } $cur->modify('+1 day'); } return $calendar; } // ✅ You can now call these functions in your main POST logic to keep code readable and modular. // Helper function for fair rotation /*function getNextCourseForSlot($remainingSessions, $courseList, $usedToday) { $available = array_filter($courseList, function($courseID) use ($remainingSessions) { return ($remainingSessions[$courseID] ?? 0) > 0; }); $unusedToday = array_filter($available, fn($courseID) => !in_array($courseID, $usedToday)); if (!empty($unusedToday)) { return reset($unusedToday); } // Allow same course in both slots if only one remains if (count($available) === 1) { return reset($available); } return null; }*/ /** * Returns the next best course ID to schedule for a given slot, given remaining sessions, priorities, and special cases. * * @param array $remainingSessions courseID => number left * @param array $candidates list of course IDs eligible for this slot * @param array $usedToday list of course IDs already scheduled *today* (to avoid doubling same course per day) * @param array $courseCodes map: courseID => course code * @param int|null $onlyCourseIDLeft if only one course remains, its ID * @return int|null the selected course ID or null if none eligible */ function getNextCourseForSlot($remainingSessions, $candidates, $usedToday, $courseCodes = [], $onlyCourseIDLeft = null) { // 1. Filter to courses that have sessions left $available = array_filter($candidates, function($cid) use ($remainingSessions) { return ($remainingSessions[$cid] ?? 0) > 0; }); // 2. Prefer a course not already used today foreach ($available as $cid) { if (!in_array($cid, $usedToday)) { return $cid; } } // 3. If only one course left (last), allow re-use if (count($available) === 1 && ($onlyCourseIDLeft === reset($available))) { return reset($available); } // 4. Special: If two courses left and one is a "special" course code, prefer it if (count($available) === 2 && !empty($courseCodes)) { foreach ($available as $cid) { $code = $courseCodes[$cid] ?? ''; if (in_array($code, ['961-238', '960-746'])) { return $cid; } } } // 5. If all else fails, just return the first available return reset($available) ?: null; } $scheduleOutput = []; // Default if ($_SERVER['REQUEST_METHOD'] === 'POST') { //if (isset($_POST['reset']) || isset($_POST['Start_Date'])) { // unset($_SESSION['deletedSaturdays']); //} // Step 1: Retrieve Form Data $forceSecondSpecialAfternoon = !empty($_POST['force_second_special_afternoon']); $lastCourseBothSlots = !empty($_POST['last_course_both_slots']); $Program_ID = intval($_POST['Program_ID']); $Group_ID = intval($_POST['Group_ID']); $Start_Date = $_POST['Start_Date']; $End_Date = $_POST['End_Date'] ?? date('Y-m-d', strtotime('+1 year', strtotime($Start_Date))); $selectedCourses = $_POST['Selected_Courses'] ?? []; $courseSessions = $_POST['Course_Sessions'] ?? []; $priorityCourses = $_POST['Priority_Course'] ?? []; $group = get_group_info($mysqli, $Group_ID); if (!$group) { echo "Group info not found."; exit; } $classDays = explode(',', $group['class_days']); if (!$group['Weekend_Class'] && in_array('Saturday', $classDays)) { $classDays = array_filter($classDays, fn($day) => $day !== 'Saturday'); } $slotLabels = explode(',', $group['Time_Slot']); $managerStart = new DateTime(trim($group['Time_From'])); $managerEnd = new DateTime(trim($group['Time_To'])); //list($timeFrom, $timeTo) = fetch_slot_times($mysqli, $slotLabels, $managerStart, $managerEnd); // Example (replace with your actual fetch code): $all_slot_program_ids = []; $group_slot_labels = []; $group_time_from = []; $group_time_to = []; $stmt = $mysqli->prepare("SELECT Group_Slot_ID,Time_Slot_Programs_ID, Time_Slot, Time_From, Time_To FROM Group_Slot_Mapping WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); $res = $stmt->get_result(); $slotLabels = $timeFrom = $timeTo = []; while ($row = $res->fetch_assoc()) { $slotLabels[] = $row['Time_Slot']; $timeFrom[] = $row['Time_From']; $timeTo[] = $row['Time_To']; $group_slot_id[] = $row['Group_Slot_ID']; $slotProgramID = $row['Time_Slot_Programs_ID']; $all_slot_program_ids[] = $slotProgramID; $group_slot_labels[$slotProgramID] = $row['Time_Slot']; $group_time_from[$slotProgramID] = $row['Time_From']; $group_time_to[$slotProgramID] = $row['Time_To']; } $sessionLength = calculate_session_length($timeFrom, $timeTo); $courseSessions = calculate_course_sessions($mysqli, $Program_ID, $sessionLength); $holidayDates = get_holidays($mysqli, $Start_Date); $retakeDates = get_retake_dates($mysqli, $Program_ID, $Group_ID); $validDates = generate_valid_dates($Start_Date, $End_Date, $classDays, $holidayDates, $retakeDates); $suggestedSessions = calculate_course_sessions($mysqli, $Program_ID, $sessionLength); $courseSessions = []; foreach ($selectedCourses as $courseID) { $custom = $_POST['Course_Sessions'][$courseID] ?? null; if (is_numeric($custom) && intval($custom) > 0) { $courseSessions[$courseID] = intval($custom); } else { $courseSessions[$courseID] = $suggestedSessions[$courseID] ?? 0; } } //echo "

📅 Valid Dates for Scheduling (Excluding July)

"; print_r($validDates); echo "
"; // 🔄 Additional scheduling logic .... // Output valid dates //echo "

📅 Valid Dates for Scheduling (Excluding July)

"; print_r($validDates); echo "
"; $totalSessionsNeeded = array_sum($courseSessions); $availableDays = count($validDates); // Estimate sessions per day (based on number of slots) $sessionsPerDay = count($slotLabels); // Calculate how many days are needed to finish all sessions $daysRequired = ceil($totalSessionsNeeded / $sessionsPerDay); // Get expected end date $expectedEndDate = $validDates[$daysRequired - 1] ?? end($validDates); // fallback to last available if out of bounds //echo "

📅 Expected End Date Based on Andragogical Days

"; //echo "

$expectedEndDate (" . (new DateTime($expectedEndDate))->format('l') . ")

"; // Sort by course priority usort($selectedCourses, function ($a, $b) use ($priorityCourses) { $priorityA = $priorityCourses[$a] ?? PHP_INT_MAX; $priorityB = $priorityCourses[$b] ?? PHP_INT_MAX; return $priorityA <=> $priorityB; }); // echo "

🔄 Sorted Courses (Priority First)

"; print_r($selectedCourses); echo "
"; // Prepare for fair scheduling (one course per slot) // Initialize $schedule = []; $remainingSessions = []; foreach ($selectedCourses as $courseID) { $remainingSessions[$courseID] = $courseSessions[$courseID] ?? 0; } // Queue of all courses $courseQueue = $selectedCourses; $currentCourses = [ 0 => null, // Slot 1 (e.g. Morning) 1 => null // Slot 2 (e.g. Afternoon) ]; // Assign initial two courses // Assign initial courses to slots without repeating $courseQueueCopy = $selectedCourses; $assignedCourses = []; foreach ([0, 1] as $slotIndex) { foreach ($courseQueueCopy as $courseID) { if (!in_array($courseID, $assignedCourses) && $remainingSessions[$courseID] > 0) { $currentCourses[$slotIndex] = $courseID; $assignedCourses[] = $courseID; break; } } } // Fetch all course names into array $courseNames = []; $courseRes = $mysqli->query("SELECT Course_ID, Course_Name FROM Courses"); while ($row = $courseRes->fetch_assoc()) { $courseNames[$row['Course_ID']] = $row['Course_Name']; } // --- Setup slot/course/retake tracking structures --- $courseSlotMap = []; // Tracks which slot a course is "locked" to (courseID => slotIndex) $specialCourseOnlyMode = false; // If only 1 "special" course is left, triggers special scheduling logic $specialCourseID = null; // Holds the ID of the "special" course (e.g. 961-238, 960-746) $lockedSlot = []; // Tracks if a course is locked to a slot (courseID => slotIndex) $pushedCourse = []; // If a course is "pushed" (blocked by a retake), store it for later in this slot (slotIndex => courseID) $numSlots = count($slotLabels); // Number of time slots per day /////###################################### /** * Pre-schedules every special course into $schedule, * tagging their last session as an exam. * * @param array &$schedule The schedule array to append into * @param array $specialCourseIDs Ordered list of special course IDs * @param array &$remainingSessions Course_ID => sessions remaining * @param array $allSlots Flat list of slot metadata * @param array $courseNames Course_ID => human name */ /** * Pre-schedules every special course into $schedule, * tagging their last session as an exam. Returns the last date/slot used. * * @return array [$lastDate, $lastSlotIndex] */ /** * Pre-schedules every special course into $schedule, * tagging their last session as an exam and forcing * the final session of the 2nd & 4th specials into an afternoon slot. * * @return array [$lastDate, $lastSlotIndex] */ function scheduleSpecialCourses( array &$schedule, array $specialCourseIDs, array &$remainingSessions, array $allSlots, array $courseNames, bool $forceSecondSpecialAfternoon // ← here signature to accept the new flag ): array { $spPtr = 0; $lastSpecialDate = null; $lastSpecialSlot = null; foreach ($specialCourseIDs as $idx => $spCid) { $toDo = $remainingSessions[$spCid] ?? 0; while ($toDo > 0 && isset($allSlots[$spPtr])) { $slot = $allSlots[$spPtr]; /* // Always force the last session (exam) into the Afternoon slot (slotIndex == 1) if ($toDo === 1 && $slot['slotIndex'] === 0) { // Search forward for afternoon slot on same day for ($j = $spPtr; $j < count($allSlots); $j++) { if ( $allSlots[$j]['date'] === $slot['date'] && $allSlots[$j]['slotIndex'] === 1 ) { $slot = $allSlots[$j]; $spPtr = $j; break; } } } */ // If user asked to force the *second* special into afternoon… if ( $forceSecondSpecialAfternoon && $toDo === 1 // last session && $slot['slotIndex'] === 0 ) { // scan forward to same-day slotIndex=1 for ($j = $spPtr; $j < count($allSlots); $j++) { if ( $allSlots[$j]['date'] === $slot['date'] && $allSlots[$j]['slotIndex'] === 1 ) { $slot = $allSlots[$j]; $spPtr = $j + 1; break; } } } $spPtr++; if (!$slot['from'] || !$slot['to']) continue; $isExam = ($toDo === 1); $schedule[] = [ 'Date' => $slot['date'], 'SlotIndex' => $slot['slotIndex'], 'Slot' => $slot['slotLabel'], 'Time' => format_time_slot_range( $slot['slotLabel'], $slot['from'], $slot['to'], $slot['dayName'] ), 'Course_ID' => $spCid, 'Course_Name' => $courseNames[$spCid] ?? '', 'Start_Time' => $slot['from'], 'End_Time' => $slot['to'], 'Group_Slot_ID' => $slot['groupSlotId'], 'Reserve_Course' => 0, 'IsExam' => $isExam, ]; $lastSpecialDate = $slot['date']; $lastSpecialSlot = $slot['slotIndex']; $toDo--; $remainingSessions[$spCid]--; } unset($remainingSessions[$spCid]); } return [$lastSpecialDate, $lastSpecialSlot]; } // --- SPECIAL COURSES SCHEDULING --- (RUNS ONCE BEFORE main foreach) // ------------------------------------------------------------------------ // ➊ Prepare your schedule array and remainingSessions as you already do… $schedule = []; // clear any prior entries // $remainingSessions is already set from your courseSessions logic // ➀ Build $specialCourseIDs from your Courses table & specialCourseCodes $specialCourseCodes = ['CST53001','961-012','SEC-CST53001','960-501']; $specialCourseIDs = []; $cRes = $mysqli->query(" SELECT Course_ID, Course_Code FROM Courses "); while ($r = $cRes->fetch_assoc()) { if (in_array($r['Course_Code'], $specialCourseCodes)) { $specialCourseIDs[] = $r['Course_ID']; } } // Global queue for any course bumped by a retake $pushQueue = []; //############################################################## //############################################################## // ➁ Build $allSlots from $validDates and your slot/time data $allSlots = []; foreach ($validDates as $date) { foreach (array_keys($slotLabels) as $si) { $gid = $group_slot_id[$si] ?? 0; if (!empty($retakeDates[$date][$gid]) || !empty($holidayDates[$date])) { continue; } $allSlots[] = [ 'date' => $date, 'slotIndex' => $si, 'slotLabel' => $slotLabels[$si], 'from' => $timeFrom[$si] ?? null, 'to' => $timeTo[$si] ?? null, 'groupSlotId'=> $gid, 'dayName' => (new DateTime($date))->format('l'), ]; } } // ➋ Front-load specials via the new function // front-load all specials, capturing where they ended list($lastSpecialDate, $lastSpecialSlot) = scheduleSpecialCourses( $schedule, $specialCourseIDs, $remainingSessions, $allSlots, $courseNames, // ← pass this in now $forceSecondSpecialAfternoon // ← here pass your flag ); /////###################################### // Mark already-used date/slot pairs $occupiedSlots = []; foreach ($schedule as $s) { $occupiedSlots[$s['Date']][$s['SlotIndex']] = true; } /////###################################### // --- Main scheduling loop over each valid date --- foreach ($validDates as $date) { $usedToday = []; // Reset courses used for this day $dayName = (new DateTime($date))->format('l'); // Get the day of week, e.g. "Monday" $activeCourses = array_filter($remainingSessions, fn($s) => $s > 0); // Only courses that still need sessions $activeCourseIDs = array_keys($activeCourses); // Their IDs // --- Detect special course only mode (e.g. last course is a "special" one) --- $specialCourseOnlyMode = false; $specialCourseID = null; if (count($activeCourseIDs) === 1) { $cid = $activeCourseIDs[0]; $code = $courseCodes[$cid] ?? ''; /* if (count($activeCourseIDs) === 1 && !$__special_sessions_adjusted) { $cid = $activeCourseIDs[0]; $code = $courseCodes[$cid] ?? ''; if (in_array($code, $specialCourseCodes) && $numSlots > 1) { //$remainingSessions[$cid] = ceil($remainingSessions[$cid] / $numSlots); //$__special_sessions_adjusted = true; } }*/ if (in_array($code, ['961-238', '960-746'])) { //if (in_array($code, ['', ''])) { $specialCourseOnlyMode = true; $specialCourseID = $cid; // 🟡 ADJUST THE SESSION COUNT if we allow special course in both slots // Only adjust ONCE! if ($numSlots > 1 && !$__special_sessions_adjusted) { $remainingSessions[$specialCourseID] = ceil($remainingSessions[$specialCourseID] / $numSlots); $__special_sessions_adjusted = true; } } } // ========== SINGLE SLOT LOGIC ========== if ($numSlots === 1) { $slotIndex = 0; $from = $timeFrom[$slotIndex] ?? null; $to = $timeTo[$slotIndex] ?? null; $slotLabel = $slotLabels[$slotIndex]; $groupSlot = $group_slot_id[$slotIndex] ?? 0; // If there's a retake for this date (regardless of slot), show retake only //if (isset($retakeDates[$date])) { // For old format where $retakeDates[$date] is string or array //$retakeType = is_array($retakeDates[$date]) ? reset($retakeDates[$date]) : $retakeDates[$date]; // Look for any slot on this date $slotIndex = 0; $groupSlotId = $group_slot_id[$slotIndex]; // should be 32 if (isset($retakeDates[$date][$groupSlotId])) { $retake = $retakeDates[$date][$groupSlotId]; $schedule[] = [ 'Date' => $date, 'Slot' => $retake['label'] ?? $slotLabels[0], // <-- Use label from retakeDates if present, else default 'Time' => format_time_slot_range($retake['label'], $retake['from'], $retake['to'], $dayName), 'Course_ID' => null, 'Course_Name' => "Retake: " . htmlspecialchars($retake['type']), 'Start_Time' => $retake['from'], 'End_Time' => $retake['to'], 'Group_Slot_ID'=> $groupSlotId, 'Reserve_Course' => 0, 'IsRetake' => true, 'Retake_Type' => $retake['type'] ]; continue; } // If a pushed (previously blocked) course exists, assign it now if (isset($pushedCourse[$slotIndex])) { $courseID = $pushedCourse[$slotIndex]; unset($pushedCourse[$slotIndex]); } else { // Otherwise, pick next available course normally if ($specialCourseOnlyMode) { $courseID = $specialCourseID; } else { $candidates = array_filter($selectedCourses, function($cid) use ($remainingSessions) { return ($remainingSessions[$cid] ?? 0) > 0; }); $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; $courseID = getNextCourseForSlot( $remainingSessions, $candidates, $usedToday, $courseCodes, $onlyCourseIDLeft ); } if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) continue; $usedToday[] = $courseID; } $courseName = $courseNames[$courseID] ?? "Course #$courseID"; $schedule[] = [ 'Date' => $date, 'Slot' => "Slot 1", 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => $courseID, 'Course_Name' => $courseName, 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $group_slot_id[$slotIndex] ?? 0, 'Reserve_Course' => 0 ]; $remainingSessions[$courseID]--; if (array_sum($remainingSessions) <= 0) break; } // ========== MULTI-SLOT LOGIC (2 or more slots) ========== else { ////##########################--- END SPECIAL SCHEDULING ---################################# // Loop through each slot (e.g. morning/afternoon) foreach (array_keys($slotLabels) as $slotIndex) { // ──────────────────────────────────────────────────────────────────────────── // Only skip slots strictly before the exam date, or the morning of the exam date // skip everything up through that exam‐morning // Don't double-book: skip if already occupied by a special course if (!empty($occupiedSlots[$date][$slotIndex])) { continue; } // ───────────────────────────────────────── // If we're down to one course, skip the morning slot… /* $onlyCourseLeft = (count($activeCourses) === 1); if ($onlyCourseLeft && $slotIndex === 0) { continue; } */ // Determine if we’re down to exactly one active course $onlyCourseLeft = count($activeCourses) === 1; // Only skip the morning slot *if* they did NOT check “both slots” if ( !$lastCourseBothSlots && $onlyCourseLeft && $slotIndex === 0 ) { continue; } // ───────────────────────────────────────── // Now do your normal multi-slot scheduling for regular courses! $from = $timeFrom[$slotIndex] ?? null; $to = $timeTo[$slotIndex] ?? null; $slotLabel = $slotLabels[$slotIndex]; $groupSlotId = $group_slot_id[$slotIndex] ?? 0; if (!$from || !$to) continue; // --- Special course only mode: skip non-morning slots //if ($specialCourseOnlyMode && $slotIndex !== 0) continue; // --- 1. RETAKE HANDLING --- if (!empty($retakeDates[$date][$groupSlotId])) { $slotInfo = $retakeDates[$date][$groupSlotId]; $retakeType = $slotInfo['type'] ?? ''; $slotLabel = $slotInfo['label'] ?? $slotLabel; $from = $slotInfo['from'] ?? $from; $to = $slotInfo['to'] ?? $to; $schedule[] = [ 'Date' => $date, 'SlotIndex' => $slotIndex, // ← NEW 'Slot' => $slotLabel, //'Slot' => "Slot " . ($slotIndex + 1), 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => null, 'Course_Name' => "Retake: " . htmlspecialchars($retakeType), 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $groupSlotId, 'Reserve_Course' => 0, 'IsRetake' => true, 'Retake_Type' => $retakeType ]; // Optionally push the blocked course for this slot if (!isset($pushedCourse[$slotIndex])) { $courseToPush = getCourseToPush( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ); if ($courseToPush !== null) { $pushedCourse[$slotIndex] = $courseToPush; } if ($courseToPush !== null) { $pushQueue[] = $courseToPush; // enqueue at tail } } continue; // Only skip normal class for this *slot*, not the whole date! } // --- 2. PUSHED COURSE HANDLING (if retake blocked a course previously) --- if (isset($pushedCourse[$slotIndex])) { $courseID = $pushedCourse[$slotIndex]; unset($pushedCourse[$slotIndex]); // If no remaining sessions for that course, skip if (($remainingSessions[$courseID] ?? 0) <= 0) continue; } else { // --- 3. NORMAL COURSE SCHEDULING --- /* $courseID = selectNextCourse( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ); if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) continue; $usedToday[] = $courseID; */ //################################################################## // --- 3. NORMAL COURSE SCHEDULING --- // ————— HERE: ONLY-ONE-COURSE-LEFT SKIP MORNING ————— /* $onlyCourseIDLeft = (count($activeCourses) === 1) ? array_key_first($activeCourses) : null; if ($onlyCourseIDLeft !== null && $slotIndex === 0) { // we’re down to one course; skip the morning slot so it lands in the afternoon continue; } // ———————————————————————————————————————————————— // --- 3. NORMAL COURSE SCHEDULING --- $courseID = getNextCourseForSlot( $remainingSessions, array_keys($activeCourses), $usedToday, $courseCodes, $onlyCourseIDLeft ); if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) { continue; } $usedToday[] = $courseID; */ //################################################################## if (count($activeCourses) === 1) { // Only one course left, allow it in all slots (don't check $usedToday) $courseID = array_key_first($activeCourses); } else { // Usual course selection logic $courseID = selectNextCourse( $specialCourseOnlyMode, $specialCourseID, $selectedCourses, $remainingSessions, $slotIndex, $lockedSlot, $courseSlotMap, $activeCourses, $usedToday, $courseCodes ); if (!$courseID || ($remainingSessions[$courseID] ?? 0) <= 0) continue; // Prevent same course twice if more than one course left if ($courseID && !in_array($courseID, $usedToday)) { $usedToday[] = $courseID; } } } // --- 4. SLOT LOCKING LOGIC --- We close it we need to show the last 2 courses one in the morning and other in afternoon /* applySlotLocking( $activeCourses, $remainingSessions, $specialCourseID, $lockedSlot, $slotIndex ); */ // Lock this course to this slot, if not already locked if (!isset($courseSlotMap[$courseID])) { $courseSlotMap[$courseID] = $slotIndex; } // --- 5. SCHEDULE THE COURSE --- $schedule[] = [ 'Date' => $date, 'SlotIndex' => $slotIndex, // ← NEW //'Slot' => $slotLabels[$slotIndex], // e.g. "Morning" or "Afternoon" 'Slot' => "Slot " . ($slotIndex + 1), 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => $courseID, 'Course_Name' => $courseNames[$courseID] ?? "Course #$courseID", 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $groupSlotId, 'Reserve_Course' => 0 ]; $remainingSessions[$courseID]--; // If all sessions are scheduled, break out of both loops if (array_sum($remainingSessions) <= 0) break 2; } } // End multi-slot else } // End foreach validDates /* // --- Make sure you already have getNextCourseForSlot() defined elsewhere in your file --- echo "

--- DEBUGGING ARRAYS ---

";

echo "
DEBUG Program_ID: $Program_ID, Group_ID: $Group_ID
"; echo "Selected Courses:\n"; print_r($selectedCourses); echo "\nCourse Sessions (per course):\n"; print_r($courseSessions); echo "\nPriority Courses:\n"; print_r($priorityCourses); echo "\nSlot Labels:\n"; print_r($slotLabels); echo "\nTime From (per slot):\n"; print_r($timeFrom); echo "\nTime To (per slot):\n"; print_r($timeTo); echo "\nGroup Slot IDs:\n"; print_r($group_slot_id); echo "\nAll Slot Program IDs:\n"; print_r($all_slot_program_ids); echo "\nGroup Slot Labels (SlotProgramID => Label):\n"; print_r($group_slot_labels); echo "\nGroup Time From (SlotProgramID => Time):\n"; print_r($group_time_from); echo "\nGroup Time To (SlotProgramID => Time):\n"; print_r($group_time_to); echo "\nHoliday Dates:\n"; print_r($holidayDates); echo "\nRetake Dates:\n"; print_r($retakeDates); echo "\nValid Dates:\n"; print_r($validDates); echo "\nCourse Names (CourseID => Name):\n"; print_r($courseNames); echo "\nCourse Slot Map (CourseID => SlotProgramID):\n"; print_r($courseSlotMap); echo "\nRemaining Sessions (CourseID => Remaining):\n"; print_r($remainingSessions); echo "\nPushed Course (SlotProgramID => CourseID):\n"; print_r($pushedCourse); echo "\nLocked Slot (CourseID => SlotProgramID):\n"; print_r($lockedSlot); echo "\nSchedule (final result):\n"; print_r($schedule); echo "
"; */ /* echo "
";
foreach ($schedule as $row) {
  echo "{$row['Date']}  slotIndex={$row['SlotIndex']}  course={$row['Course_ID']}  IsExam=".($row['IsExam']?1:0)."\n";
}
echo "
"; */ // 🔍 Identify last scheduled session for each course $lastSessions = []; foreach ($schedule as $index => $entry) { $courseID = $entry['Course_ID']; $lastSessions[$courseID] = $index; // keeps overwriting until the last index } //echo "

📘 Final Generated Schedule

"; print_r($schedule); echo "
"; //echo "

📋 Generated Schedule Table

"; //echo "

Group: " . htmlspecialchars($groupName) . "

"; if (!empty($schedule)) { // Collect all displayed dates: scheduled, holidays, and retakes $displayDates = []; // 1. From scheduled sessions foreach ($schedule as $row) { $displayDates[$row['Date']][] = $row; // grouped by date } // 2. Add holidays (if no course scheduled on them) foreach ($holidayDates as $hDate => $title) { if (!isset($displayDates[$hDate])) { $displayDates[$hDate] = []; // Add empty row so we render it } } foreach ($retakeDates as $rDate => $slots) { if (!isset($displayDates[$rDate])) { $displayDates[$rDate] = []; } // For each slot with a retake on this date foreach ($slots as $groupSlotId => $slotInfo) { // Check if we already have a retake for this slot/date $alreadyRetake = false; foreach ($displayDates[$rDate] as $row) { if (!empty($row['IsRetake']) && ($row['Slot'] == ($slotInfo['label'] ?? ''))) { $alreadyRetake = true; break; } } if (!$alreadyRetake) { $displayDates[$rDate][] = [ 'Slot' => $slotInfo['label'] ?? null, // e.g., "Evening" 'Time' => isset($slotInfo['from'], $slotInfo['to'], $slotInfo['label']) ? format_time_slot_range($slotInfo['label'], $slotInfo['from'], $slotInfo['to'], (new DateTime($rDate))->format('l')) : null, 'Course_ID' => null, 'IsRetake' => true, 'Retake_Type' => $slotInfo['type'] ?? null ]; } } } //echo "
";
//echo "\nSisplayDates (final all result):\n";
//print_r($displayDates);
//
//echo "
"; // Sort the dates chronologically ksort($displayDates); } else { echo "

🚫 No schedule generated — check if valid time slots, sessions, or dates are missing.

"; } } /* $scheduleOutput = []; // Default if ($_SERVER['REQUEST_METHOD'] === 'POST') { //if (isset($_POST['reset']) || isset($_POST['Start_Date'])) { // unset($_SESSION['deletedSaturdays']); //} // Step 1: Retrieve Form Data $Program_ID = intval($_POST['Program_ID']); $Group_ID = intval($_POST['Group_ID']); $Start_Date = $_POST['Start_Date']; $End_Date = $_POST['End_Date'] ?? date('Y-m-d', strtotime('+1 year', strtotime($Start_Date))); $selectedCourses = $_POST['Selected_Courses'] ?? []; $courseSessions = $_POST['Course_Sessions'] ?? []; $priorityCourses = $_POST['Priority_Course'] ?? []; $group = get_group_info($mysqli, $Group_ID); if (!$group) { echo "Group info not found."; exit; } $classDays = explode(',', $group['class_days']); if (!$group['Weekend_Class'] && in_array('Saturday', $classDays)) { $classDays = array_filter($classDays, fn($day) => $day !== 'Saturday'); } $slotLabels = explode(',', $group['Time_Slot']); $managerStart = new DateTime(trim($group['Time_From'])); $managerEnd = new DateTime(trim($group['Time_To'])); //list($timeFrom, $timeTo) = fetch_slot_times($mysqli, $slotLabels, $managerStart, $managerEnd); $stmt = $mysqli->prepare("SELECT Group_Slot_ID, Time_Slot, Time_From, Time_To FROM Group_Slot_Mapping WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); $res = $stmt->get_result(); $slotLabels = $timeFrom = $timeTo = []; while ($row = $res->fetch_assoc()) { $slotLabels[] = $row['Time_Slot']; $timeFrom[] = $row['Time_From']; $timeTo[] = $row['Time_To']; $group_slot_id[] = $row['Group_Slot_ID']; } $sessionLength = calculate_session_length($timeFrom, $timeTo); $courseSessions = calculate_course_sessions($mysqli, $Program_ID, $sessionLength); $holidayDates = get_holidays($mysqli, $Start_Date); $retakeDates = get_retake_dates($mysqli, $Program_ID, $Group_ID); $validDates = generate_valid_dates($Start_Date, $End_Date, $classDays, $holidayDates, $retakeDates); //echo "

📅 Valid Dates for Scheduling (Excluding July)

"; print_r($validDates); echo "
"; // 🔄 Additional scheduling logic .... // Output valid dates //echo "

📅 Valid Dates for Scheduling (Excluding July)

"; print_r($validDates); echo "
"; $totalSessionsNeeded = array_sum($courseSessions); $availableDays = count($validDates); // Estimate sessions per day (based on number of slots) $sessionsPerDay = count($slotLabels); // Calculate how many days are needed to finish all sessions $daysRequired = ceil($totalSessionsNeeded / $sessionsPerDay); // Get expected end date $expectedEndDate = $validDates[$daysRequired - 1] ?? end($validDates); // fallback to last available if out of bounds //echo "

📅 Expected End Date Based on Andragogical Days

"; //echo "

$expectedEndDate (" . (new DateTime($expectedEndDate))->format('l') . ")

"; // Sort by course priority usort($selectedCourses, function ($a, $b) use ($priorityCourses) { $priorityA = $priorityCourses[$a] ?? PHP_INT_MAX; $priorityB = $priorityCourses[$b] ?? PHP_INT_MAX; return $priorityA <=> $priorityB; }); // echo "

🔄 Sorted Courses (Priority First)

"; print_r($selectedCourses); echo "
"; // Prepare for fair scheduling (one course per slot) // Initialize $schedule = []; $remainingSessions = []; foreach ($selectedCourses as $courseID) { $remainingSessions[$courseID] = $courseSessions[$courseID] ?? 0; } // Queue of all courses $courseQueue = $selectedCourses; $currentCourses = [ 0 => null, // Slot 1 (e.g. Morning) 1 => null // Slot 2 (e.g. Afternoon) ]; // Assign initial two courses // Assign initial courses to slots without repeating $courseQueueCopy = $selectedCourses; $assignedCourses = []; foreach ([0, 1] as $slotIndex) { foreach ($courseQueueCopy as $courseID) { if (!in_array($courseID, $assignedCourses) && $remainingSessions[$courseID] > 0) { $currentCourses[$slotIndex] = $courseID; $assignedCourses[] = $courseID; break; } } } // Fetch all course names into array $courseNames = []; $courseRes = $mysqli->query("SELECT Course_ID, Course_Name FROM Courses"); while ($row = $courseRes->fetch_assoc()) { $courseNames[$row['Course_ID']] = $row['Course_Name']; } // before you start scheduling: $courseSlotMap = []; // Start scheduling loop // Start scheduling loop foreach ($validDates as $date) { $usedToday = []; // Reset per day $dayName = (new DateTime($date))->format('l'); foreach ([0, 1] as $slotIndex) { $from = $timeFrom[$slotIndex] ?? null; $to = $timeTo[$slotIndex] ?? null; // skip morning slot (index 0) if it's a retake day if (isset($retakeDates[$date]) && $slotIndex === 0) { continue; } if (!$from || !$to) continue; // 1) Filter only courses eligible for this slot $candidates = array_filter($selectedCourses, function($cid) use ($remainingSessions, $courseSlotMap, $slotIndex) { // must still have sessions remaining if (empty($remainingSessions[$cid])) { return false; } // if we've already locked this course to the other slot, skip it if (isset($courseSlotMap[$cid]) && $courseSlotMap[$cid] !== $slotIndex) { return false; } return true; }); // 2) Pick from that filtered list $courseID = getNextCourseForSlot($remainingSessions, $candidates, $usedToday); if (!$courseID) continue; // 3) Lock it into this slot if it wasn't already if (!isset($courseSlotMap[$courseID])) { $courseSlotMap[$courseID] = $slotIndex; } $slotLabel = $slotLabels[$slotIndex]; $courseName = $courseNames[$courseID] ?? "Course #$courseID"; // 4) Save the scheduled session //$schedule[] = [ // 'Date' => $date, // 'Slot' => "Slot " . ($slotIndex + 1), // 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), // 'Course_ID' => $courseID //]; // 4) Save the scheduled session (with times + slot ID) $schedule[] = [ 'Date' => $date, 'Slot' => "Slot " . ($slotIndex + 1), 'Time' => format_time_slot_range($slotLabel, $from, $to, $dayName), 'Course_ID' => $courseID, 'Course_Name' => $courseNames[$courseID] ?? '', // ← add this 'Start_Time' => $from, 'End_Time' => $to, 'Group_Slot_ID' => $group_slot_id[$slotIndex] ?? 0, 'Reserve_Course' => 0 ]; // 5) Decrement and mark used $remainingSessions[$courseID]--; $usedToday[] = $courseID; // 6) Stop if done if (array_sum($remainingSessions) <= 0) { break 2; } } } // 🔍 Identify last scheduled session for each course $lastSessions = []; foreach ($schedule as $index => $entry) { $courseID = $entry['Course_ID']; $lastSessions[$courseID] = $index; // keeps overwriting until the last index } //echo "

📘 Final Generated Schedule

"; print_r($schedule); echo "
"; //echo "

📋 Generated Schedule Table

"; //echo "

Group: " . htmlspecialchars($groupName) . "

"; if (!empty($schedule)) { // Collect all displayed dates: scheduled, holidays, and retakes $displayDates = []; // 1. From scheduled sessions foreach ($schedule as $row) { $displayDates[$row['Date']][] = $row; // grouped by date } // 2. Add holidays (if no course scheduled on them) foreach ($holidayDates as $hDate => $title) { if (!isset($displayDates[$hDate])) { $displayDates[$hDate] = []; // Add empty row so we render it } } // 3. Add retakes //foreach ($retakeDates as $rDate => $type) { // if (!isset($displayDates[$rDate])) { // $displayDates[$rDate] = []; // } //} // 3. Ensure every retake date has a morning placeholder foreach ($retakeDates as $rDate => $type) { // make sure the date key exists if (!isset($displayDates[$rDate])) { $displayDates[$rDate] = []; } // detect if we already have a slot-1 (afternoon) row but no slot-0 $hasMorning = false; foreach ($displayDates[$rDate] as $row) { if (isset($row['Slot']) && trim($row['Slot']) === 'Slot 1') { $hasMorning = true; break; } } if (!$hasMorning) { array_unshift($displayDates[$rDate], [ 'Slot' => null, 'Time' => null, 'Course_ID' => null, 'IsRetake' => true // our flag ]); } } // Sort the dates chronologically ksort($displayDates); } else { echo "

🚫 No schedule generated — check if valid time slots, sessions, or dates are missing.

"; } } */ ?> Generate Group Schedule

Generate Group Schedule

prepare("SELECT Course_ID, Course_Name, Course_Time FROM Courses WHERE Program_ID = ?"); //$stmtCourses->bind_param("i", $Program_ID); //$stmtCourses->execute(); //$resultCourses = $stmtCourses->get_result(); // //while ($course = $resultCourses->fetch_assoc()) { // $suggestedSessions = ceil($course['Course_Time'] / 3); // Example: assume each session is 3 hours // echo " // // // // // // "; //} //$stmtCourses->close(); } ?>
Select Course Suggested Sessions Custom Sessions Prioritize?
" . htmlspecialchars($course['Course_Name']) . "
// //
"; if (!empty($displayDates)) { echo ""; // Fetch group info before rendering table $stmt = $mysqli->prepare("SELECT Group_Name FROM Manager_Group_Name WHERE Group_ID = ?"); $stmt->bind_param("i", $Group_ID); $stmt->execute(); $stmt->bind_result($groupName); $stmt->fetch(); $stmt->close(); // Show group name before the table echo "
"; echo "

"; echo "Schedule for " . htmlspecialchars($groupName) . ""; echo "

"; echo "
"; echo ""; echo ""; $ajaxScheduleData = []; foreach ($displayDates as $date => $rows) { $dayName = (new DateTime($date))->format('l'); $isHoliday = array_key_exists($date, $holidayDates); $isRetake = array_key_exists($date, $retakeDates); // 🟡 Case: No sessions on this day if (empty($rows)) { $label = "No Sessions"; $bg = "#F9F9F9"; if ($isHoliday) { $label = "Holiday: " . htmlspecialchars($holidayDates[$date]); $bg = "#FFD700"; } elseif ($isRetake) { $label = "Retake: " . htmlspecialchars($retakeDates[$date]); $bg = "#CCE5FF"; } echo ""; continue; } // 🗓️ Loop through scheduled classes on that day foreach ($rows as $row) { // --- Determine slot name correctly --- if (!empty($row['IsRetake'])) { // For retakes, the Slot already contains the correct human label (e.g., "Evening") $slotName = $row['Slot'] ?? '-'; } else if ($numSlots > 1) { // For multi-slot, use the slot index to get the label $slotIndex = (int) filter_var($row['Slot'], FILTER_SANITIZE_NUMBER_INT) - 1; // instead of parsing numbers, just use the string // $slotName = $row['Slot']; $slotName = isset($slotLabels[$slotIndex]) ? format_time_slot_label($slotLabels[$slotIndex], $dayName) : 'Unknown'; } else { // For single slot, use the only slot label $slotName = $slotLabels[0] ?? '-'; } // Universal slot label resolver /* if (!empty($row['IsRetake'])) { $slotName = $row['Slot'] ?? '-'; } else { // If Slot is numeric (e.g., "Slot 1") use slotLabels, else show as is if (preg_match('/^Slot (\d+)$/', $row['Slot'], $m)) { $slotIdx = intval($m[1]) - 1; $slotName = $slotLabels[$slotIdx] ?? $row['Slot']; } else { $slotName = $row['Slot']; // Already human label } } */ // --- Display row --- if (!empty($row['IsRetake'])) { echo ""; continue; } //$slotIndex = (int) filter_var($row['Slot'], FILTER_SANITIZE_NUMBER_INT) - 1; //$slotName = isset($slotLabels[$slotIndex]) // ? format_time_slot_label($slotLabels[$slotIndex], $dayName) // : 'Unknown'; // //if (!empty($row['IsRetake'])) { // echo " // // // // // // "; // continue; //} // ——— now your existing “real” row rendering ——— $courseName = $courseNames[$row['Course_ID']] ?? "Course ID {$row['Course_ID']}"; // More reliable way to find session index $index = null; foreach ($schedule as $i => $entry) { if ( $entry['Course_ID'] === $row['Course_ID'] && $entry['Date'] === $row['Date'] && $entry['Slot'] === $row['Slot'] ) { $index = $i; break; } } $isExam = isset($lastSessions[$row['Course_ID']]) && $index === $lastSessions[$row['Course_ID']]; // Highlight $style = ""; if ($isExam) { $style = "style='background-color: #FFCCCC; font-weight: bold;'"; } elseif ($isHoliday) { $style = "style='background-color: #FFF5D1;'"; } elseif ($isRetake) { $style = "style='background-color: #D9F0FF;'"; } $slotIndex = (int) filter_var($row['Slot'], FILTER_SANITIZE_NUMBER_INT) - 1; $slotName = isset($slotLabels[$slotIndex]) ? format_time_slot_label($slotLabels[$slotIndex], $dayName) : 'Unknown'; if(!($dayName === 'Saturday')) { if (preg_match('/^Slot (\d+)$/', $row['Slot'], $m)) { $slotIdx = intval($m[1]) - 1; $slotName = $slotLabels[$slotIdx] ?? $row['Slot']; } else { $slotName = $row['Slot']; // Already human label (Morning, Saturday, etc) } } // If Slot is numeric (e.g., "Slot 1") use slotLabels, else show as is /* if (preg_match('/^Slot (\d+)$/', $row['Slot'], $m)) { $slotIdx = intval($m[1]) - 1; $slotName = $slotLabels[$slotIdx] ?? $row['Slot']; } else { $slotName = $row['Slot']; // Already human label } */ // for both single- and multi-slot, trust the Slot label you already set //$slotName = $row['Slot']; $isSaturday = ($dayName === 'Saturday'); $actionBtn = $isSaturday ? " " : "-"; $ajaxScheduleData[] = [ 'program_id' => $Program_ID, 'course_id' => $row['Course_ID'], 'course_name' => $courseNames[$row['Course_ID']] ?? '', 'group_id' => $Group_ID, 'group_slot_id' => $row['Group_Slot_ID'], 'reserve_course' => $row['Reserve_Course'], 'time_slot' => $slotName, 'start_date' => $row['Date'], 'end_date' => $row['Date'], 'start_time' => $row['Start_Time'], 'end_time' => $row['End_Time'] ]; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } } echo ""; echo "
DateSlotTimeCourseAction
($dayName) $date --$label-
($dayName) $date {$slotName} {$row['Time']} " . htmlspecialchars($row['Retake_Type']) . " -
($dayName) $date{$slotName}{$row['Time']}" . htmlspecialchars($row['Retake_Type']) . "-
($dayName) {$row['Date']}{$slotName}{$row['Time']}{$courseName}" . ($isExam ? " (Exam Day)" : "") . "{$actionBtn}
DateSlotTimeCourseAction
"; // *** Right here, after $ajaxScheduleData is fully built: *** echo "\n"; } ?>