const db = require("../db"); const { reportError } = require("../lib/report-error"); // Weekly schedule repository methods (PostgreSQL mode; no DEV_MOCK branch in // the original handlers, so this module is used in both modes). async function updateWeeklyScheduleStatus(req) { try { const scheduleId = Number(req.params.scheduleId); const { status } = req.body; const validStatuses = [ "planned", "in_progress", "completed", "cancelled", "active", ]; if (!Number.isInteger(scheduleId) || !validStatuses.includes(status)) { return { statusCode: 400, body: { error: "invalid_status" } }; } const normalizedStatus = status === "active" ? "in_progress" : status; const result = await db.query( ` UPDATE schedules s SET status = $1, updated_at = NOW() WHERE s.id = $2 AND EXISTS ( SELECT 1 FROM shifts sh WHERE sh.schedule_id = s.id AND sh.driver_id = $3 ) RETURNING s.id, s.status `, [normalizedStatus, scheduleId, req.user.userId], ); if (result.rows.length === 0) return { statusCode: 404, body: { error: "schedule_not_found" } }; return { statusCode: 200, body: { schedule: { id: Number(result.rows[0].id), status: result.rows[0].status, }, }, }; } catch (err) { console.error("PATCH /driver/weekly-schedules/:id/status error:", err); reportError({ partofservice: "PATCH /driver/weekly-schedules/:id/status", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } } async function listWeeklySchedules(req) { try { const result = await db.query(` SELECT s.id, s.bus_line_id, s.start_time, s.end_time, s.status, s.day_of_week, bl.line_number, bl.start_stop, bl.end_stop, sh.id AS shift_id, sh.driver_id, u.usercode AS driver_name FROM schedules s JOIN bus_lines bl ON bl.id = s.bus_line_id LEFT JOIN shifts sh ON sh.schedule_id = s.id LEFT JOIN workers.users u ON u.id = sh.driver_id ORDER BY s.day_of_week ASC, s.start_time ASC, s.id ASC `); return { statusCode: 200, body: { schedules: result.rows.map((row) => ({ id: Number(row.id), busLineId: Number(row.bus_line_id), lineNumber: row.line_number, startStop: row.start_stop, endStop: row.end_stop, startTime: row.start_time, endTime: row.end_time, status: row.status, dayOfWeek: Number(row.day_of_week), shiftId: row.shift_id ? Number(row.shift_id) : null, driverId: row.driver_id ? Number(row.driver_id) : null, driverName: row.driver_name || null, })), }, }; } catch (err) { console.error("GET /admin/weekly-schedules error:", err); reportError({ partofservice: "GET /admin/weekly-schedules", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } } async function createWeeklySchedule(req) { try { const busLineId = Number(req.body?.busLineId); const dayOfWeek = Number(req.body?.dayOfWeek); const startTime = String(req.body?.startTime || ""); const endTime = String(req.body?.endTime || ""); const driverId = req.body?.driverId ? Number(req.body.driverId) : null; if ( !Number.isInteger(busLineId) || !Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6 || !startTime || !endTime ) { return { statusCode: 400, body: { error: "busLineId, dayOfWeek, startTime and endTime are required", }, }; } const created = await db.query( ` INSERT INTO schedules (bus_line_id, start_time, end_time, status, day_of_week) VALUES ($1, $2::time, $3::time, 'planned', $4) RETURNING id, bus_line_id, start_time, end_time, status, day_of_week `, [busLineId, startTime, endTime, dayOfWeek], ); const schedule = created.rows[0]; if (driverId) { await db.query( ` INSERT INTO shifts (schedule_id, driver_id) VALUES ($1, $2) `, [schedule.id, driverId], ); } return { statusCode: 201, body: { schedule: { id: Number(schedule.id), busLineId: Number(schedule.bus_line_id), startTime: schedule.start_time, endTime: schedule.end_time, status: schedule.status, dayOfWeek: Number(schedule.day_of_week), driverId, }, }, }; } catch (err) { console.error("POST /admin/weekly-schedules error:", err); reportError({ partofservice: "POST /admin/weekly-schedules", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } } async function bulkCreateWeeklySchedules(req) { const busLineId = Number(req.body?.busLineId); const dayOfWeeks = Array.isArray(req.body?.dayOfWeeks) ? req.body.dayOfWeeks .map(Number) .filter((day) => Number.isInteger(day) && day >= 0 && day <= 6) : []; const startTimes = Array.isArray(req.body?.startTimes) ? [...new Set(req.body.startTimes.map(String).filter(Boolean))] : []; const driverId = req.body?.driverId ? Number(req.body.driverId) : null; if ( !Number.isInteger(busLineId) || dayOfWeeks.length === 0 || startTimes.length === 0 ) { return { statusCode: 400, body: { error: "busLineId, dayOfWeeks and startTimes are required" }, }; } const client = await db.pool.connect(); try { await client.query("BEGIN"); const line = await client.query( "SELECT estimated_duration_minutes FROM bus_lines WHERE id = $1", [busLineId], ); if (line.rows.length === 0) { await client.query("ROLLBACK"); return { statusCode: 404, body: { error: "bus_line_not_found" } }; } const duration = Number(line.rows[0].estimated_duration_minutes) || 60; const created = []; for (const dayOfWeek of dayOfWeeks) { for (const startTime of startTimes) { const result = await client.query( `INSERT INTO schedules (bus_line_id, start_time, end_time, status, day_of_week) VALUES ($1, $2::time, ($2::time + ($3::text || ' minutes')::interval)::time, 'planned', $4) RETURNING id, bus_line_id, start_time, end_time, status, day_of_week`, [busLineId, startTime, duration, dayOfWeek], ); const schedule = result.rows[0]; if (driverId) { await client.query( "INSERT INTO shifts (schedule_id, driver_id) VALUES ($1, $2)", [schedule.id, driverId], ); } created.push(schedule); } } await client.query("COMMIT"); return { statusCode: 201, body: { createdCount: created.length, schedules: created.map((schedule) => ({ id: Number(schedule.id), busLineId: Number(schedule.bus_line_id), startTime: schedule.start_time, endTime: schedule.end_time, status: schedule.status, dayOfWeek: Number(schedule.day_of_week), driverId, })), }, }; } catch (err) { await client.query("ROLLBACK"); console.error("POST /admin/weekly-schedules/bulk error:", err); reportError({ partofservice: "POST /admin/weekly-schedules/bulk", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } finally { client.release(); } } async function updateWeeklySchedule(req) { try { const scheduleId = Number(req.params.scheduleId); const busLineId = Number(req.body?.busLineId); const dayOfWeek = Number(req.body?.dayOfWeek); const startTime = String(req.body?.startTime || ""); const endTime = String(req.body?.endTime || ""); if ( !Number.isInteger(scheduleId) || !Number.isInteger(busLineId) || !Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6 || !startTime || !endTime ) { return { statusCode: 400, body: { error: "invalid_schedule" } }; } const result = await db.query( ` UPDATE schedules SET bus_line_id = $2, start_time = $3::time, end_time = $4::time, day_of_week = $5, updated_at = NOW() WHERE id = $1 RETURNING id, bus_line_id, start_time, end_time, status, day_of_week `, [scheduleId, busLineId, startTime, endTime, dayOfWeek], ); if (result.rows.length === 0) return { statusCode: 404, body: { error: "schedule_not_found" } }; return { statusCode: 200, body: { schedule: result.rows[0] } }; } catch (err) { console.error("PUT /admin/weekly-schedules error:", err); reportError({ partofservice: "PUT /admin/weekly-schedules", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } } async function updateWeeklyScheduleShift(req) { try { const scheduleId = Number(req.params.scheduleId); const driverId = req.body?.driverId ? Number(req.body.driverId) : null; if (!Number.isInteger(scheduleId)) return { statusCode: 400, body: { error: "invalid_schedule_id" } }; await db.query("DELETE FROM shifts WHERE schedule_id = $1", [ scheduleId, ]); if (driverId) { await db.query( "INSERT INTO shifts (schedule_id, driver_id) VALUES ($1, $2)", [scheduleId, driverId], ); } return { statusCode: 200, body: { scheduleId, driverId } }; } catch (err) { console.error("PUT /admin/weekly-schedules/:id/shift error:", err); reportError({ partofservice: "PUT /admin/weekly-schedules/:id/shift", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } } async function deleteWeeklySchedule(req) { try { const scheduleId = Number(req.params.scheduleId); if (!Number.isInteger(scheduleId)) return { statusCode: 400, body: { error: "invalid_schedule_id" } }; const result = await db.query( "DELETE FROM schedules WHERE id = $1 RETURNING id", [scheduleId], ); if (result.rows.length === 0) return { statusCode: 404, body: { error: "schedule_not_found" } }; return { statusCode: 200, body: { success: true, scheduleId } }; } catch (err) { console.error("DELETE /admin/weekly-schedules error:", err); reportError({ partofservice: "DELETE /admin/weekly-schedules", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } } module.exports = { updateWeeklyScheduleStatus, listWeeklySchedules, createWeeklySchedule, bulkCreateWeeklySchedules, updateWeeklySchedule, updateWeeklyScheduleShift, deleteWeeklySchedule, };