const { store: _mock } = require("../mock/store"); const { calculateDepartureTimes, expandRecurringScheduleRows, scheduleMatchesFilters, parseScheduleFilters } = require("../lib/schedule"); const { reportError } = require("../lib/report-error"); // Admin schedules repository methods (DEV_MOCK mode). These are exact copies of // the original handlers’ mock branches, returning { statusCode, body }. async function listAdminSchedules(req) { try { const templatesOnly = String( req.query.templates || req.query.includeTemplates || "", ).toLowerCase() === "true" || String(req.query.templates || req.query.includeTemplates || "") === "1"; const routeStopsByLine = new Map(); for (const routeStop of _mock.routeStops || []) { const busLineId = Number(routeStop.busLineId); if (!routeStopsByLine.has(busLineId)) { routeStopsByLine.set(busLineId, []); } routeStopsByLine.get(busLineId).push({ stop_id: routeStop.stopId, stop_order: routeStop.stopOrder, }); } const rawSchedules = (_mock.schedules || []).map((s) => { const busLine = _mock.busLines.find((bl) => bl.id === s.busLineId); const bus = _mock.fleetBuses.find((b) => b.id === s.busId); return { ...s, busLineId: s.busLineId, busId: s.busId, startTime: s.startTime, endTime: s.endTime, weekdays: s.weekdays || [], status: s.status, lineNumber: busLine?.lineNumber || 0, busName: bus?.name || "Unknown Bus", driverId: s.driverId || null, estimatedDurationMinutes: busLine?.estimatedDurationMinutes || 60, }; }); const schedules = ( templatesOnly ? rawSchedules : expandRecurringScheduleRows(rawSchedules) ) .filter((row) => scheduleMatchesFilters( row, parseScheduleFilters(req.query), routeStopsByLine, ), ) .map((s) => ({ ...s, departureTimes: calculateDepartureTimes( s.startTime, s.busLineId, _mock, ), })) .sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); return { statusCode: 200, body: { schedules } }; } catch (err) { console.error("GET /admin/schedules error:", err); reportError({ partofservice: "GET /admin/schedules", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error", details: err.message } }; } } async function createAdminSchedule(req) { try { const { busLineId, busId, driverId, startTime, weekdays } = req.body; if (!busLineId || !busId || !startTime) { return { statusCode: 400, body: { error: "missing_required_fields: busLineId, busId, startTime required", }, }; } const scheduleWeekdays = Array.isArray(weekdays) && weekdays.length > 0 ? weekdays.map((w) => Number(w)).filter((w) => w >= 0 && w <= 6) : []; if (!_mock.schedules) _mock.schedules = []; const busLine = _mock.busLines.find((bl) => bl.id == busLineId); if (!busLine) { return { statusCode: 404, body: { error: "bus_line_not_found" } }; } const startDate = new Date(startTime); const endDate = new Date(startDate); endDate.setMinutes( endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60), ); const departureTimes = calculateDepartureTimes( startTime, Number(busLineId), _mock, ); const schedule = { id: _mock.nextScheduleId || 1, busLineId, busId, driverId: driverId ? Number(driverId) : null, startTime, endTime: endDate.toISOString(), weekdays: scheduleWeekdays, departureTimes, status: "planned", createdAt: new Date().toISOString(), }; _mock.nextScheduleId = (_mock.nextScheduleId || 1) + 1; _mock.schedules.push(schedule); return { statusCode: 201, body: { schedule } }; } catch (err) { console.error(err); reportError({ partofservice: `${req.method} ${req.originalUrl}`, error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error", details: err.message } }; } } async function updateAdminSchedule(req) { try { const { scheduleId } = req.params; const { busLineId, busId, driverId, startTime, weekdays } = req.body; if (!scheduleId || !Number.isFinite(Number(scheduleId))) { return { statusCode: 400, body: { error: "invalid_schedule_id" } }; } if (!busLineId || !busId || !startTime) { return { statusCode: 400, body: { error: "missing_required_fields: busLineId, busId, startTime required", }, }; } const scheduleWeekdays = Array.isArray(weekdays) && weekdays.length > 0 ? weekdays.map((w) => Number(w)).filter((w) => w >= 0 && w <= 6) : []; const index = (_mock.schedules || []).findIndex( (s) => Number(s.id) === Number(scheduleId), ); if (index === -1) { return { statusCode: 404, body: { error: "schedule_not_found" } }; } const busLine = _mock.busLines.find((bl) => bl.id == busLineId); if (!busLine) { return { statusCode: 404, body: { error: "bus_line_not_found" } }; } const startDate = new Date(startTime); const endDate = new Date(startDate); endDate.setMinutes( endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60), ); _mock.schedules[index] = { ..._mock.schedules[index], busLineId: Number(busLineId), busId: Number(busId), driverId: driverId ? Number(driverId) : null, startTime, endTime: endDate.toISOString(), weekdays: scheduleWeekdays, }; return { statusCode: 200, body: { schedule: _mock.schedules[index] } }; } catch (err) { console.error(err); reportError({ partofservice: `${req.method} ${req.originalUrl}`, error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error", details: err.message } }; } } async function bulkCreateAdminSchedules(req) { const requestedSchedules = Array.isArray(req.body?.schedules) ? req.body.schedules : []; if (requestedSchedules.length === 0) { return { statusCode: 400, body: { error: "missing_required_fields: schedules array required" }, }; } const normalizedSchedules = requestedSchedules.map((schedule, index) => { const busLineId = Number(schedule?.busLineId); const busId = Number(schedule?.busId); const driverId = schedule?.driverId !== null && schedule?.driverId !== undefined && schedule?.driverId !== "" ? Number(schedule.driverId) : null; const startTime = schedule?.startTime ? String(schedule.startTime) : ""; const weekdays = Array.isArray(schedule?.weekdays) && schedule.weekdays.length > 0 ? schedule.weekdays .map((day) => Number(day)) .filter((day) => day >= 0 && day <= 6) : []; if ( !Number.isFinite(busLineId) || !Number.isFinite(busId) || !startTime ) { throw new Error(`invalid_schedule_entry:${index}`); } return { busLineId, busId, driverId, startTime, weekdays }; }); try { if (!_mock.schedules) _mock.schedules = []; const createdSchedules = []; for (const entry of normalizedSchedules) { const busLine = _mock.busLines.find((bl) => bl.id == entry.busLineId); if (!busLine) { return { statusCode: 404, body: { error: "bus_line_not_found" } }; } const startDate = new Date(entry.startTime); const endDate = new Date(startDate); endDate.setMinutes( endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60), ); const departureTimes = calculateDepartureTimes( entry.startTime, Number(entry.busLineId), _mock, ); const schedule = { id: _mock.nextScheduleId || 1, busLineId: entry.busLineId, busId: entry.busId, driverId: entry.driverId, startTime: entry.startTime, endTime: endDate.toISOString(), weekdays: entry.weekdays, departureTimes, status: "planned", createdAt: new Date().toISOString(), }; _mock.nextScheduleId = (_mock.nextScheduleId || 1) + 1; _mock.schedules.push(schedule); createdSchedules.push(schedule); } return { statusCode: 201, body: { createdCount: createdSchedules.length, schedules: createdSchedules, }, }; } catch (err) { console.error(err); reportError({ partofservice: "POST /admin/schedules/bulk", error: err.stack || err.message }); const errorMessage = err.message === "bus_line_not_found" ? "bus_line_not_found" : err.message.startsWith("invalid_schedule_entry:") ? err.message : "internal_error"; const statusCode = errorMessage === "bus_line_not_found" ? 404 : errorMessage.startsWith("invalid_schedule_entry:") ? 400 : 500; return { statusCode, body: { error: errorMessage, details: err.message } }; } } async function deleteAdminSchedule(req) { try { const { scheduleId } = req.params; if (!scheduleId || !Number.isFinite(Number(scheduleId))) { return { statusCode: 400, body: { error: "invalid_schedule_id" } }; } if (!_mock.schedules) _mock.schedules = []; const index = _mock.schedules.findIndex( (s) => Number(s.id) === Number(scheduleId), ); if (index === -1) { return { statusCode: 404, body: { error: "schedule_not_found" } }; } const deleted = _mock.schedules.splice(index, 1)[0]; return { statusCode: 200, body: { success: true, schedule: deleted } }; } catch (err) { console.error(err); reportError({ partofservice: `${req.method} ${req.originalUrl}`, error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error", details: err.message } }; } } async function assignSchedules(req) { try { const { assignments, busLineId, busId, weekdays = [] } = req.body; if (!Array.isArray(assignments)) { return { statusCode: 400, body: { error: "missing_assignments" } }; } const schedulesToCreate = []; let driverIndex = 0; for (const assignment of assignments) { if (!assignment.time || !assignment.assignedDriverId) continue; const startTime = assignment.time; const endTime = assignment.endTime; schedulesToCreate.push({ busLineId, busId, driverId: assignment.assignedDriverId, startTime, endTime, weekdays, }); } if (!_mock.schedules) _mock.schedules = []; const createdSchedules = []; for (const entry of schedulesToCreate) { const schedule = { id: _mock.nextScheduleId || 1, ...entry, status: "planned", createdAt: new Date().toISOString(), }; _mock.nextScheduleId = (_mock.nextScheduleId || 1) + 1; _mock.schedules.push(schedule); createdSchedules.push(schedule); } return { statusCode: 201, body: { createdCount: createdSchedules.length, schedules: createdSchedules, }, }; } catch (err) { console.error("/admin/assign-schedules error:", err); reportError({ partofservice: "/admin/assign-schedules", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error", details: err.message } }; } } module.exports = { listAdminSchedules, createAdminSchedule, updateAdminSchedule, bulkCreateAdminSchedules, deleteAdminSchedule, assignSchedules, };