php js css php js css

Changes

Changes to Script: Inspect AF Courses & Course Schedules Units components

235814
Today 4:53am
McNaught, Scott
Modified script
235815
Today 4:53am
McNaught, Scott
Modified script
Last Run:
Modified:
Id:
SRiug5aJoEIbXx1y
SRiug5aJoEIbXx1y
Title:
Inspect AF Courses & Course Schedules Units components
Inspect AF Courses & Course Schedules Units components
Code:
<?php
/**
* Deep inspection + demo data generator: AF Courses, Schedules, Units.
* Creates 50 realistic courses with future schedules (Term 1-4 2027),
* products, schedules and per-week units across levels/age groups.
*/

$iCoursesTarget = 50;

// ---------- 1. Inspect existing data ----------
echo "=== INSPECTION ===\n";
echo "Courses total: " . \Components\AF\Courses::getCount() . "\n";
echo "Schedules total: " . \Components\AF\Courses\Schedules::getCount() . "\n";
echo "Units total: " . \Components\AF\Courses\Units::getCount() . "\n";
echo "Sessions: " . \Components\AF\Courses\Configuration\Sessions::getCount() . "\n";
echo "Levels: " . \Components\AF\Courses\Configuration\Levels::getCount() . "\n";
echo "Products: " . \Components\Commerce\Products::getCount() . "\n\n";

// ---------- 2. Ensure future sessions exist (Term 2-4 2027) ----------
$arrNewSessions = [
['2027', 'Term 2 2027', '2027-04-19', '2027-06-19'],
['2027', 'Term 3 2027', '2027-07-12', '2027-09-11'],
['2027', 'Term 4 2027', '2027-10-04', '2027-12-04'],
];
$arrSessionIds = [51 => ['2027-02-01', '2027-03-28']]; // existing Term 1 2027
foreach ($arrNewSessions as $arrS)
{
$arrExisting = \Components\AF\Courses\Configuration\Sessions::getAll([
'filter' => [['session_name', '=', $arrS[1]], 'AND', ['session_year', '=', $arrS[0]]],
'limit' => 1,
]);
if (count($arrExisting))
{
$iSid = (int)$arrExisting[0]['session_id'];
}
else
{
$iSid = (int)\Components\AF\Courses\Configuration\Sessions::save([
'session_year' => $arrS[0],
'session_name' => $arrS[1],
'session_start_date' => $arrS[2],
'session_end_date' => $arrS[3],
'session_visible_public' => 1,
]);
}
$arrSessionIds[$iSid] = [$arrS[2], $arrS[3]];
}
echo "Sessions available: " . implode(', ', array_keys($arrSessionIds)) . "\n";

// ---------- 3. Course blueprint builder ----------
$arrTeachers = [1351, 1181, 1052, 1180, 1182];
// Rooms: CBD Campus(2): 7,8,9,23,24 | St Kilda(1): 3,4,5,6 | Online Zooms(17): 19,20,21
$arrRooms = [
'cbd' => [7, 8, 9, 23, 24],
'stkilda' => [3, 4, 5, 6],
'online' => [19, 20, 21],
];
$arrDayNames = [1 => 'Mon', 2 => 'Tue', 3 => 'Wed', 4 => 'Thu', 5 => 'Fri', 6 => 'Sat', 7 => 'Sun'];
$arrFullDayNames = [1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday', 7 => 'sunday'];

$fnCourse = function($arrDef) {
return $arrDef;
};

// Build 50 course definitions: [group, level, levelName, ageGroup, type, pace, hoursPerClass, timeStart, timeEnd, days, campusKey]
$arrCourses = [];

// Adults - Standard progression (16)
foreach ([
['A1.1', 2], ['A1.2', 7], ['A1.3', 8], ['A1.4', 9],
['A2.1', 10], ['A2.2', 11], ['A2.3', 12], ['A2.4', 131],
['B1.1', 14], ['B1.2', 15], ['B1.3', 16],
['B2.1', 17], ['B2.2', 18], ['C1', 6], ['C2', 132], ['A2.5', 27],
] as $i => $arrLvl)
{
$iDay = ($i % 5) + 1;
$bEvening = ($i % 2 === 0);
$arrCourses[] = [
'level' => $arrLvl[0], 'level_id' => $arrLvl[1], 'age' => 1, 'type' => 2,
'pace' => 38, 'hpc' => 2.0, 'start' => $bEvening ? '18:00:00' : '09:30:00', 'end' => $bEvening ? '20:00:00' : '11:30:00',
'days' => [$iDay], 'campus' => ($i % 4 === 3) ? 'online' : 'cbd', 'teacher' => $arrTeachers[$i % 5],
];
}
// Adults - speciality (8)
$arrSpecial = [
['Conversation B1', 14, 29, 39, 1.5, '18:00:00', '19:30:00', 2],
['Conversation B2', 17, 29, 39, 1.5, '12:00:00', '13:30:00', 4],
['French for Travel A1', 2, 27, 39, 1.5, '17:30:00', '19:00:00', 5],
['Easy Going A2', 10, 30, 39, 1.5, '10:00:00', '11:30:00', 3],
['DELF B2 Prep', 17, 24, 38, 2.0, '17:00:00', '19:00:00', 6],
['Grammar Workshop B1', 14, 16, 44, 6.0, '09:30:00', '15:30:00', 6],
['Pronunciation Workshop A2', 10, 16, 44, 6.0, '09:30:00', '15:30:00', 6],
['TCF Exam Prep', 130, 24, 38, 2.0, '18:00:00', '20:00:00', 1],
];
foreach ($arrSpecial as $i => $a)
{
$arrCourses[] = [
'level' => $a[0], 'level_id' => $a[1], 'age' => 1, 'type' => $a[2],
'pace' => $a[3], 'hpc' => $a[4], 'start' => $a[5], 'end' => $a[6],
'days' => [$a[7]], 'campus' => ($i === 7) ? 'online' : 'stkilda', 'teacher' => $arrTeachers[($i + 2) % 5],
];
}
// Teens (10)
foreach ([['Year 7', 22], ['Year 8', 23], ['Year 9', 24], ['Year 10', 133], ['Year 11', 134], ['Year 12', 135]] as $i => $arrLvl)
{
$arrCourses[] = [
'level' => $arrLvl[0], 'level_id' => $arrLvl[1], 'age' => 2, 'type' => ($i % 3 === 0 ? 23 : 2),
'pace' => 38, 'hpc' => 2.0, 'start' => '16:30:00', 'end' => '18:30:00',
'days' => [($i % 4) + 1], 'campus' => ($i % 3 === 2) ? 'online' : 'cbd', 'teacher' => $arrTeachers[($i + 1) % 5],
];
}
foreach ([['Teens Conversation A2', 10, 29, 5], ['DELF Junior A2 Prep', 10, 24, 6], ['Taste of French Y9', 24, 23, 3], ['Taste of French Y11', 134, 23, 4]] as $i => $a)
{
$arrCourses[] = [
'level' => $a[0], 'level_id' => $a[1], 'age' => 2, 'type' => $a[2],
'pace' => 39, 'hpc' => 1.5, 'start' => '16:45:00', 'end' => '18:15:00',
'days' => [$a[3]], 'campus' => 'stkilda', 'teacher' => $arrTeachers[($i + 3) % 5],
];
}
// Children (10)
foreach ([['Prep', 136], ['Year 1', 137], ['Year 2', 138], ['Year 3', 139]] as $i => $arrLvl)
{
$arrCourses[] = [
'level' => $arrLvl[0], 'level_id' => $arrLvl[1], 'age' => 3, 'type' => 2,
'pace' => 39, 'hpc' => 1.5, 'start' => '10:00:00', 'end' => '11:30:00',
'days' => [6], 'campus' => 'stkilda', 'teacher' => $arrTeachers[$i % 5],
];
}
foreach ([['Taste of French Kids 1', 136, 22, 6], ['Taste of French Kids 2', 137, 22, 6], ['Kids Holiday Immersion A1', 2, 25, 2], ['Kids Holiday Immersion A2', 130, 25, 2], ['Kids Conversation B1', 14, 29, 3]] as $i => $a)
{
$arrCourses[] = [
'level' => $a[0], 'level_id' => $a[1], 'age' => 3, 'type' => $a[2],
'pace' => 39, 'hpc' => 1.5, 'start' => '13:00:00', 'end' => '14:30:00',
'days' => [$a[3]], 'campus' => ($i > 1 ? 'cbd' : 'stkilda'), 'teacher' => $arrTeachers[($i + 4) % 5],
];
}
// Toddlers (4)
foreach ([['French Play Group', 21, '09:30:00', '10:15:00'], ['Mum & Me', 19, '10:30:00', '11:15:00'], ['Story Time', 20, '11:30:00', '12:15:00'], ['French Play Group 2', 21, '09:30:00', '10:15:00']] as $i => $a)
{
$arrCourses[] = [
'level' => 'Pre-School', 'level_id' => 1, 'age' => 12, 'type' => $a[1],
'pace' => 37, 'hpc' => 0.75, 'start' => $a[2], 'end' => $a[3],
'days' => [($i % 2) + 5], 'campus' => 'stkilda', 'teacher' => $arrTeachers[$i % 5],
];
}
// Corporate + Intensive (2)
$arrCourses[] = [
'level' => 'A1.1 Corporate', 'level_id' => 2, 'age' => 4, 'type' => 28,
'pace' => 38, 'hpc' => 2.0, 'start' => '08:00:00', 'end' => '10:00:00',
'days' => [2, 4], 'campus' => 'cbd', 'teacher' => $arrTeachers[0],
];
$arrCourses[] = [
'level' => 'A1 Intensive', 'level_id' => 1, 'age' => 1, 'type' => 3,
'pace' => 45, 'hpc' => 4.0, 'start' => '09:00:00', 'end' => '13:00:00',
'days' => [1, 2, 3, 4, 5], 'campus' => 'online', 'teacher' => $arrTeachers[1],
];

$arrCourses = array_slice($arrCourses, 0, $iCoursesTarget);
echo "Course definitions: " . count($arrCourses) . "\n\n";

// ---------- 4. Create courses ----------
$arrSessionKeys = array_keys($arrSessionIds);
$iCreated = 0;
$arrErrors = [];

foreach ($arrCourses as $i => $a)
{
// Assign sessions round-robin, then a start date inside the session window
$iSessionId = $arrSessionKeys[$i % count($arrSessionKeys)];
list($sSessStart, $sSessEnd) = $arrSessionIds[$iSessionId];
$iDayNum = $a['days'][0];
$iOffset = ($i * 7) % 28; // vary start week
$oStart = new DateTime($sSessStart);
$oStart->modify('+' . $iOffset . ' days');
while ((int)$oStart->format('N') !== $iDayNum)
{
$oStart->modify('+1 day');
}
// 8 weekly classes per schedule
$oEnd = clone $oStart;
$oEnd->modify('+' . (count($a['days']) * 7) . ' days');

$sCourseCode = $a['level'] . '-' . $arrDayNames[$iDayNum] . '-' . substr(str_replace('-', '', $sSessStart), 0, 8) . '-' . ($i + 1);

// Product
if (!isset($iBrandId))
{
$arrBrand = \Components\Commerce\Products\Brands::getAll(['limit' => 1]);
echo "Brand row sample: " . json_encode($arrBrand[0] ?? null) . "\n";
if (count($arrBrand))
{
$iBrandId = (int)$arrBrand[0]['brand_id'];
}
else
{
$iBrandId = (int)\Components\Commerce\Products\Brands::save(['brand_name' => 'Alliance Française']);
}
echo "Using brand: {$iBrandId}\n";
}
$fHoursTotal = $a['hpc'] * 8;
$fPrice = round(($fHoursTotal * 55) / 10) * 10;
$iProductId = 0;
$arrProd = \Components\Commerce\Products::getAll(['filter' => [['product_code', '=', $sCourseCode]], 'limit' => 1]);
if (count($arrProd))
{
$iProductId = (int)$arrProd[0]['product_id'];
}
else
{
$iProductId = (int)\Components\Commerce\Products::save([
'product_title' => $a['level'] . ' ' . $arrDayNames[$iDayNum] . ' ' . ($iSessionId === 51 ? 'Term 1 2027' : '2027'),
'product_price_base' => $fPrice,
'product_code' => $sCourseCode,
'product_brand_id' => $iBrandId,
'product_is_offline' => 1,
]);
}

$arrLocationIds = $arrRooms[$a['campus']];
$iLocationId = $arrLocationIds[$i % count($arrLocationIds)];
$sLocationType = ($a['campus'] === 'online') ? 'remote' : 'internal';

// Course
$arrCourse = [
'course_code' => $sCourseCode,
'course_type' => 'collective',
'course_start_date' => $oStart->format('Y-m-d'),
'course_start_time' => $a['start'],
'course_end_date' => $oEnd->format('Y-m-d'),
'course_end_time' => $a['end'],
'course_total_hours' => number_format($fHoursTotal, 2, '.', ''),
'course_objectives' => $a['level'] . ' objectives: develop listening and speaking skills, build core vocabulary and consolidate grammar through interactive activities.',
'session_id' => $iSessionId,
'age_group_id' => $a['age'],
'type_id' => $a['type'],
'pace_id' => $a['pace'],
'product_id' => $iProductId,
'location_ids_cache' => (string)$iLocationId,
'level_ids_cache' => (string)$a['level_id'],
'course_is_day_' . $arrFullDayNames[$iDayNum] => 1,
'course_hidden_in_selector' => 0,
'course_membership_option' => 'hidden',
];
$iCourseId = (int)\Components\AF\Courses::save($arrCourse);
if (!$iCourseId)
{
$arrErrors[] = "Course save failed: {$sCourseCode}";
continue;
}

// Level link
\Components\AF\Courses\Levels::save(['course_id' => $iCourseId, 'level_id' => $a['level_id']]);

// Schedule
$arrSchedule = [
'schedule_start_date' => $oStart->format('Y-m-d'),
'schedule_end_date' => $oEnd->format('Y-m-d'),
'schedule_start_time' => $a['start'],
'schedule_end_time' => $a['end'],
'schedule_location_type' => $sLocationType,
'location_id' => $iLocationId,
'teacher_id' => $a['teacher'],
'course_id' => $iCourseId,
'schedule_day' => $a['days'],
];
$iScheduleId = (int)\Components\AF\Courses\Schedules::save($arrSchedule);

// Units - one per class occurrence
$iUnits = 0;
$oUnitDate = clone $oStart;
for ($u = 0; $u < 8; $u++)
{
// step to the next configured weekday
while (!in_array((int)$oUnitDate->format('N'), $a['days']))
{
$oUnitDate->modify('+1 day');
}
\Components\AF\Courses\Units::save([
'unit_date' => $oUnitDate->format('Y-m-d'),
'unit_start_time' => $a['start'],
'unit_end_time' => $a['end'],
'unit_comment' => ($u === 0 ? 'Introduction and placement' : null),
'unit_billing_status' => 'unpaid',
'unit_location_type' => $sLocationType,
'location_id' => $iLocationId,
'teacher_id' => $a['teacher'],
'schedule_id' => $iScheduleId,
'course_id' => $iCourseId,
]);
$iUnits++;
$oUnitDate->modify('+1 day');
}

$iCreated++;
echo sprintf("#%02d course_id=%d sched=%d units=%d %s | %s %s-%s | session %d | lvl %s | product %d \$%.2f\n",
$iCreated, $iCourseId, $iScheduleId, $iUnits, $sCourseCode,
$oStart->format('d M Y'), $a['start'], substr($a['end'], 0, 5), $iSessionId, $a['level'], $iProductId, $fPrice);

\Components\Settings\Developer\Threads::setCurrentProgressStatus("Created {$iCreated} / " . count($arrCourses) . " courses…");
}

echo "\n=== SUMMARY ===\n";
echo "Created courses: {$iCreated}\n";
echo "Courses total now: " . \Components\AF\Courses::getCount() . "\n";
echo "Schedules total now: " . \Components\AF\Courses\Schedules::getCount() . "\n";
echo "Units total now: " . \Components\AF\Courses\Units::getCount() . "\n";
if (count($arrErrors))
{
echo "Errors:\n" . implode("\n", $arrErrors) . "\n";
}
else
{
echo "No errors.\n";
}
<?php
/**
* Deep inspection + demo data generator: AF Courses, Schedules, Units.
* Creates 50 realistic courses with future schedules (Term 1-4 2027),
* products, schedules and per-week units across levels/age groups.
*/

$iCoursesTarget = 50;

// ---------- 1. Inspect existing data ----------
echo "=== INSPECTION ===\n";
echo "Courses total: " . \Components\AF\Courses::getCount() . "\n";
echo "Schedules total: " . \Components\AF\Courses\Schedules::getCount() . "\n";
echo "Units total: " . \Components\AF\Courses\Units::getCount() . "\n";
echo "Sessions: " . \Components\AF\Courses\Configuration\Sessions::getCount() . "\n";
echo "Levels: " . \Components\AF\Courses\Configuration\Levels::getCount() . "\n";
echo "Products: " . \Components\Commerce\Products::getCount() . "\n\n";

// ---------- 2. Ensure future sessions exist (Term 2-4 2027) ----------
$arrNewSessions = [
['2027', 'Term 2 2027', '2027-04-19', '2027-06-19'],
['2027', 'Term 3 2027', '2027-07-12', '2027-09-11'],
['2027', 'Term 4 2027', '2027-10-04', '2027-12-04'],
];
$arrSessionIds = [51 => ['2027-02-01', '2027-03-28']]; // existing Term 1 2027
foreach ($arrNewSessions as $arrS)
{
$arrExisting = \Components\AF\Courses\Configuration\Sessions::getAll([
'filter' => [['session_name', '=', $arrS[1]], 'AND', ['session_year', '=', $arrS[0]]],
'limit' => 1,
]);
if (count($arrExisting))
{
$iSid = (int)$arrExisting[0]['session_id'];
}
else
{
$iSid = (int)\Components\AF\Courses\Configuration\Sessions::save([
'session_year' => $arrS[0],
'session_name' => $arrS[1],
'session_start_date' => $arrS[2],
'session_end_date' => $arrS[3],
'session_visible_public' => 1,
]);
}
$arrSessionIds[$iSid] = [$arrS[2], $arrS[3]];
}
echo "Sessions available: " . implode(', ', array_keys($arrSessionIds)) . "\n";

// ---------- 3. Course blueprint builder ----------
$arrTeachers = [1351, 1181, 1052, 1180, 1182];
// Rooms: CBD Campus(2): 7,8,9,23,24 | St Kilda(1): 3,4,5,6 | Online Zooms(17): 19,20,21
$arrRooms = [
'cbd' => [7, 8, 9, 23, 24],
'stkilda' => [3, 4, 5, 6],
'online' => [19, 20, 21],
];
$arrDayNames = [1 => 'Mon', 2 => 'Tue', 3 => 'Wed', 4 => 'Thu', 5 => 'Fri', 6 => 'Sat', 7 => 'Sun'];
$arrFullDayNames = [1 => 'monday', 2 => 'tuesday', 3 => 'wednesday', 4 => 'thursday', 5 => 'friday', 6 => 'saturday', 7 => 'sunday'];

$fnCourse = function($arrDef) {
return $arrDef;
};

// Build 50 course definitions: [group, level, levelName, ageGroup, type, pace, hoursPerClass, timeStart, timeEnd, days, campusKey]
$arrCourses = [];

// Adults - Standard progression (16)
foreach ([
['A1.1', 2], ['A1.2', 7], ['A1.3', 8], ['A1.4', 9],
['A2.1', 10], ['A2.2', 11], ['A2.3', 12], ['A2.4', 131],
['B1.1', 14], ['B1.2', 15], ['B1.3', 16],
['B2.1', 17], ['B2.2', 18], ['C1', 6], ['C2', 132], ['A2.5', 27],
] as $i => $arrLvl)
{
$iDay = ($i % 5) + 1;
$bEvening = ($i % 2 === 0);
$arrCourses[] = [
'level' => $arrLvl[0], 'level_id' => $arrLvl[1], 'age' => 1, 'type' => 2,
'pace' => 38, 'hpc' => 2.0, 'start' => $bEvening ? '18:00:00' : '09:30:00', 'end' => $bEvening ? '20:00:00' : '11:30:00',
'days' => [$iDay], 'campus' => ($i % 4 === 3) ? 'online' : 'cbd', 'teacher' => $arrTeachers[$i % 5],
];
}
// Adults - speciality (8)
$arrSpecial = [
['Conversation B1', 14, 29, 39, 1.5, '18:00:00', '19:30:00', 2],
['Conversation B2', 17, 29, 39, 1.5, '12:00:00', '13:30:00', 4],
['French for Travel A1', 2, 27, 39, 1.5, '17:30:00', '19:00:00', 5],
['Easy Going A2', 10, 30, 39, 1.5, '10:00:00', '11:30:00', 3],
['DELF B2 Prep', 17, 24, 38, 2.0, '17:00:00', '19:00:00', 6],
['Grammar Workshop B1', 14, 16, 44, 6.0, '09:30:00', '15:30:00', 6],
['Pronunciation Workshop A2', 10, 16, 44, 6.0, '09:30:00', '15:30:00', 6],
['TCF Exam Prep', 130, 24, 38, 2.0, '18:00:00', '20:00:00', 1],
];
foreach ($arrSpecial as $i => $a)
{
$arrCourses[] = [
'level' => $a[0], 'level_id' => $a[1], 'age' => 1, 'type' => $a[2],
'pace' => $a[3], 'hpc' => $a[4], 'start' => $a[5], 'end' => $a[6],
'days' => [$a[7]], 'campus' => ($i === 7) ? 'online' : 'stkilda', 'teacher' => $arrTeachers[($i + 2) % 5],
];
}
// Teens (10)
foreach ([['Year 7', 22], ['Year 8', 23], ['Year 9', 24], ['Year 10', 133], ['Year 11', 134], ['Year 12', 135]] as $i => $arrLvl)
{
$arrCourses[] = [
'level' => $arrLvl[0], 'level_id' => $arrLvl[1], 'age' => 2, 'type' => ($i % 3 === 0 ? 23 : 2),
'pace' => 38, 'hpc' => 2.0, 'start' => '16:30:00', 'end' => '18:30:00',
'days' => [($i % 4) + 1], 'campus' => ($i % 3 === 2) ? 'online' : 'cbd', 'teacher' => $arrTeachers[($i + 1) % 5],
];
}
foreach ([['Teens Conversation A2', 10, 29, 5], ['DELF Junior A2 Prep', 10, 24, 6], ['Taste of French Y9', 24, 23, 3], ['Taste of French Y11', 134, 23, 4]] as $i => $a)
{
$arrCourses[] = [
'level' => $a[0], 'level_id' => $a[1], 'age' => 2, 'type' => $a[2],
'pace' => 39, 'hpc' => 1.5, 'start' => '16:45:00', 'end' => '18:15:00',
'days' => [$a[3]], 'campus' => 'stkilda', 'teacher' => $arrTeachers[($i + 3) % 5],
];
}
// Children (10)
foreach ([['Prep', 136], ['Year 1', 137], ['Year 2', 138], ['Year 3', 139]] as $i => $arrLvl)
{
$arrCourses[] = [
'level' => $arrLvl[0], 'level_id' => $arrLvl[1], 'age' => 3, 'type' => 2,
'pace' => 39, 'hpc' => 1.5, 'start' => '10:00:00', 'end' => '11:30:00',
'days' => [6], 'campus' => 'stkilda', 'teacher' => $arrTeachers[$i % 5],
];
}
foreach ([['Taste of French Kids 1', 136, 22, 6], ['Taste of French Kids 2', 137, 22, 6], ['Kids Holiday Immersion A1', 2, 25, 2], ['Kids Holiday Immersion A2', 130, 25, 2], ['Kids Conversation B1', 14, 29, 3]] as $i => $a)
{
$arrCourses[] = [
'level' => $a[0], 'level_id' => $a[1], 'age' => 3, 'type' => $a[2],
'pace' => 39, 'hpc' => 1.5, 'start' => '13:00:00', 'end' => '14:30:00',
'days' => [$a[3]], 'campus' => ($i > 1 ? 'cbd' : 'stkilda'), 'teacher' => $arrTeachers[($i + 4) % 5],
];
}
// Toddlers (4)
foreach ([['French Play Group', 21, '09:30:00', '10:15:00'], ['Mum & Me', 19, '10:30:00', '11:15:00'], ['Story Time', 20, '11:30:00', '12:15:00'], ['French Play Group 2', 21, '09:30:00', '10:15:00']] as $i => $a)
{
$arrCourses[] = [
'level' => 'Pre-School', 'level_id' => 1, 'age' => 12, 'type' => $a[1],
'pace' => 37, 'hpc' => 0.75, 'start' => $a[2], 'end' => $a[3],
'days' => [($i % 2) + 5], 'campus' => 'stkilda', 'teacher' => $arrTeachers[$i % 5],
];
}
// Corporate + Intensive (2)
$arrCourses[] = [
'level' => 'A1.1 Corporate', 'level_id' => 2, 'age' => 4, 'type' => 28,
'pace' => 38, 'hpc' => 2.0, 'start' => '08:00:00', 'end' => '10:00:00',
'days' => [2, 4], 'campus' => 'cbd', 'teacher' => $arrTeachers[0],
];
$arrCourses[] = [
'level' => 'A1 Intensive', 'level_id' => 1, 'age' => 1, 'type' => 3,
'pace' => 45, 'hpc' => 4.0, 'start' => '09:00:00', 'end' => '13:00:00',
'days' => [1, 2, 3, 4, 5], 'campus' => 'online', 'teacher' => $arrTeachers[1],
];

$arrCourses = array_slice($arrCourses, 0, $iCoursesTarget);
echo "Course definitions: " . count($arrCourses) . "\n\n";

// ---------- 4. Create courses ----------
$arrSessionKeys = array_keys($arrSessionIds);
$iCreated = 0;
$arrErrors = [];

foreach ($arrCourses as $i => $a)
{
// Assign sessions round-robin, then a start date inside the session window
$iSessionId = $arrSessionKeys[$i % count($arrSessionKeys)];
list($sSessStart, $sSessEnd) = $arrSessionIds[$iSessionId];
$iDayNum = $a['days'][0];
$iOffset = ($i * 7) % 28; // vary start week
$oStart = new DateTime($sSessStart);
$oStart->modify('+' . $iOffset . ' days');
while ((int)$oStart->format('N') !== $iDayNum)
{
$oStart->modify('+1 day');
}
// 8 weekly classes per schedule
$oEnd = clone $oStart;
$oEnd->modify('+' . (count($a['days']) * 7) . ' days');

$sCourseCode = $a['level'] . '-' . $arrDayNames[$iDayNum] . '-' . substr(str_replace('-', '', $sSessStart), 0, 8) . '-' . ($i + 1);

// Product
if (!isset($iBrandId))
{
$arrBrand = \Components\Commerce\Products\Brands::getAll(['limit' => 1]);
echo "Brand row sample: " . json_encode($arrBrand[0] ?? null) . "\n";
if (count($arrBrand))
{
$iBrandId = (int)$arrBrand[0]['brand_id'];
}
else
{
$iBrandId = (int)\Components\Commerce\Products\Brands::save(['brand_name' => 'Alliance Française']);
}
echo "Using brand: {$iBrandId}\n";
}
$fHoursTotal = $a['hpc'] * 8;
$fPrice = round(($fHoursTotal * 55) / 10) * 10;
$iProductId = 0;
$arrProd = \Components\Commerce\Products::getAll(['filter' => [['product_code', '=', $sCourseCode]], 'limit' => 1]);
if (count($arrProd))
{
$iProductId = (int)$arrProd[0]['product_id'];
}
else
{
$iProductId = (int)\Components\Commerce\Products::save([
'product_title' => $a['level'] . ' ' . $arrDayNames[$iDayNum] . ' ' . ($iSessionId === 51 ? 'Term 1 2027' : '2027'),
'product_price_base' => $fPrice,
'product_code' => $sCourseCode,
'product_brand_id' => $iBrandId,
'product_is_offline' => 1,
]);
}

$arrLocationIds = $arrRooms[$a['campus']];
$iLocationId = $arrLocationIds[$i % count($arrLocationIds)];
$sLocationType = ($a['campus'] === 'online') ? 'remote' : 'internal';

// Course
$arrCourse = [
'course_code' => $sCourseCode,
'course_type' => 'collective',
'course_start_date' => $oStart->format('Y-m-d'),
'course_start_time' => $a['start'],
'course_end_date' => $oEnd->format('Y-m-d'),
'course_end_time' => $a['end'],
'course_total_hours' => number_format($fHoursTotal, 2, '.', ''),
'course_objectives' => $a['level'] . ' objectives: develop listening and speaking skills, build core vocabulary and consolidate grammar through interactive activities.',
'session_id' => $iSessionId,
'age_group_id' => $a['age'],
'type_id' => $a['type'],
'pace_id' => $a['pace'],
'product_id' => $iProductId,
'location_ids_cache' => (string)$iLocationId,
'level_ids_cache' => (string)$a['level_id'],
'course_is_day_' . $arrFullDayNames[$iDayNum] => 1,
'course_hidden_in_selector' => 0,
'course_membership_option' => 'hidden',
];
$iCourseId = (int)\Components\AF\Courses::save($arrCourse);
if (!$iCourseId)
{
$arrErrors[] = "Course save failed: {$sCourseCode}";
continue;
}

// Level link
\Components\AF\Courses\Levels::save(['course_id' => $iCourseId, 'level_id' => $a['level_id']]);

// Schedule
$arrSchedule = [
'schedule_start_date' => $oStart->format('Y-m-d'),
'schedule_end_date' => $oEnd->format('Y-m-d'),
'schedule_start_time' => $a['start'],
'schedule_end_time' => $a['end'],
'schedule_location_type' => $sLocationType,
'location_id' => $iLocationId,
'teacher_id' => $a['teacher'],
'course_id' => $iCourseId,
'schedule_day' => $a['days'],
];
$iScheduleId = (int)\Components\AF\Courses\Schedules::save($arrSchedule);

// Units - one per class occurrence
$iUnits = 0;
$oUnitDate = clone $oStart;
for ($u = 0; $u < 8; $u++)
{
// step to the next configured weekday
while (!in_array((int)$oUnitDate->format('N'), $a['days']))
{
$oUnitDate->modify('+1 day');
}
\Components\AF\Courses\Units::save([
'unit_date' => $oUnitDate->format('Y-m-d'),
'unit_start_time' => $a['start'],
'unit_end_time' => $a['end'],
'unit_comment' => ($u === 0 ? 'Introduction and placement' : null),
'unit_billing_status' => 'unpaid',
'unit_location_type' => $sLocationType,
'location_id' => $iLocationId,
'teacher_id' => $a['teacher'],
'schedule_id' => $iScheduleId,
'course_id' => $iCourseId,
]);
$iUnits++;
$oUnitDate->modify('+1 day');
}

$iCreated++;
echo sprintf("#%02d course_id=%d sched=%d units=%d %s | %s %s-%s | session %d | lvl %s | product %d \$%.2f\n",
$iCreated, $iCourseId, $iScheduleId, $iUnits, $sCourseCode,
$oStart->format('d M Y'), $a['start'], substr($a['end'], 0, 5), $iSessionId, $a['level'], $iProductId, $fPrice);

\Components\Settings\Developer\Threads::setCurrentProgressStatus("Created {$iCreated} / " . count($arrCourses) . " courses…");
}

echo "\n=== SUMMARY ===\n";
echo "Created courses: {$iCreated}\n";
echo "Courses total now: " . \Components\AF\Courses::getCount() . "\n";
echo "Schedules total now: " . \Components\AF\Courses\Schedules::getCount() . "\n";
echo "Units total now: " . \Components\AF\Courses\Units::getCount() . "\n";
if (count($arrErrors))
{
echo "Errors:\n" . implode("\n", $arrErrors) . "\n";
}
else
{
echo "No errors.\n";
}
Created:
2026-09-04 20:56:27
2026-09-04 20:56:27
Is Ai Generated:
Is Readonly:
Summary:
This script fills your course booking system with sample (practice) data for the year 2027. Nothing existing gets deleted or changed — it only adds new records. Here's what it will create:

**1. Up to 3 new school terms for 2027**
It adds Terms 2, 3 and 4 of 2027 (with their start and end dates) if they don't already exist. Term 1 2027 is assumed to already be there.

**2. 50 new courses**
These are French classes covering all age groups — adults, teenagers, children, toddlers, plus one corporate class and one intensive adult class. Each course gets:
- A name/code based on its level, day and term
- A start and end date within one of the 2027 terms
- A level, age group, class type and pace attached to it
- A teacher assigned from a fixed list of 5 teachers
- A room: either a city campus room, a St Kilda room, or an online (Zoom) room

**3. 50 new schedules**
One timetable entry per course, showing when and where the class runs and who teaches it.

**4. About 400 new class sessions (units)**
Each course gets 8 individual class meetings, one per week, each with a date, start and end time, room and teacher. The first class of each course is labelled as an introduction/placement session.

**5. Up to 50 new shop products**
Each course gets a matching product so people could pay for it, priced at roughly $55 per hour of teaching (rounded to the nearest $10). If a product with the same code already exists, it reuses that one instead of creating a duplicate.

**In short:** running this adds roughly 3 terms, 50 courses, 50 timetables, ~400 individual class dates, and up to 50 purchasable products to your system — all dated in 2027. It won't touch or remove any existing courses, students, or orders. If you run it more than once, it will create duplicates (except products, which are reused).
This script fills your course booking system with sample (practice) data for the year 2027. Nothing existing gets deleted or changed — it only adds new records. Here's what it will create:

**1. Up to 3 new school terms for 2027**
It adds Terms 2, 3 and 4 of 2027 (with their start and end dates) if they don't already exist. Term 1 2027 is assumed to already be there.

**2. 50 new courses**
These are French classes covering all age groups — adults, teenagers, children, toddlers, plus one corporate class and one intensive adult class. Each course gets:
- A name/code based on its level, day and term
- A start and end date within one of the 2027 terms
- A level, age group, class type and pace attached to it
- A teacher assigned from a fixed list of 5 teachers
- A room: either a city campus room, a St Kilda room, or an online (Zoom) room

**3. 50 new schedules**
One timetable entry per course, showing when and where the class runs and who teaches it.

**4. About 400 new class sessions (units)**
Each course gets 8 individual class meetings, one per week, each with a date, start and end time, room and teacher. The first class of each course is labelled as an introduction/placement session.

**5. Up to 50 new shop products**
Each course gets a matching product so people could pay for it, priced at roughly $55 per hour of teaching (rounded to the nearest $10). If a product with the same code already exists, it reuses that one instead of creating a duplicate.

**In short:** running this adds roughly 3 terms, 50 courses, 50 timetables, ~400 individual class dates, and up to 50 purchasable products to your system — all dated in 2027. It won't touch or remove any existing courses, students, or orders. If you run it more than once, it will create duplicates (except products, which are reused).
Contact Id:
-2
-2