Add lessons Phase 4: lesson sessions with hybrid calendar generation
Individual lesson occurrences generated from schedule slot patterns. Idempotent session generation with configurable rolling window. Post-lesson notes workflow with auto-set notesCompletedAt. Status tracking (scheduled/attended/missed/makeup/cancelled) and date/time filtering. 13 new tests (64 total lessons tests).
This commit is contained in:
@@ -10,8 +10,11 @@ import {
|
||||
EnrollmentCreateSchema,
|
||||
EnrollmentUpdateSchema,
|
||||
EnrollmentStatusUpdateSchema,
|
||||
LessonSessionStatusUpdateSchema,
|
||||
LessonSessionNotesSchema,
|
||||
LessonSessionUpdateSchema,
|
||||
} from '@lunarfront/shared/schemas'
|
||||
import { InstructorService, LessonTypeService, ScheduleSlotService, EnrollmentService } from '../../services/lesson.service.js'
|
||||
import { InstructorService, LessonTypeService, ScheduleSlotService, EnrollmentService, LessonSessionService } from '../../services/lesson.service.js'
|
||||
|
||||
export const lessonRoutes: FastifyPluginAsync = async (app) => {
|
||||
// --- Instructors ---
|
||||
@@ -206,4 +209,68 @@ export const lessonRoutes: FastifyPluginAsync = async (app) => {
|
||||
if (!enrollment) return reply.status(404).send({ error: { message: 'Enrollment not found', statusCode: 404 } })
|
||||
return reply.send(enrollment)
|
||||
})
|
||||
|
||||
app.post('/enrollments/:id/generate-sessions', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
||||
const { id } = request.params as { id: string }
|
||||
const query = request.query as Record<string, string | undefined>
|
||||
const weeks = query.weeks ? Number(query.weeks) : 4
|
||||
const sessions = await LessonSessionService.generateSessions(app.db, id, weeks)
|
||||
return reply.send({ generated: sessions.length, sessions })
|
||||
})
|
||||
|
||||
// --- Lesson Sessions ---
|
||||
|
||||
app.get('/lesson-sessions', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
||||
const query = request.query as Record<string, string | undefined>
|
||||
const params = PaginationSchema.parse(query)
|
||||
const filters = {
|
||||
enrollmentId: query.enrollmentId,
|
||||
instructorId: query.instructorId,
|
||||
status: query.status?.split(',').filter(Boolean),
|
||||
dateFrom: query.dateFrom,
|
||||
dateTo: query.dateTo,
|
||||
}
|
||||
const result = await LessonSessionService.list(app.db, params, filters)
|
||||
return reply.send(result)
|
||||
})
|
||||
|
||||
app.get('/lesson-sessions/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.view')] }, async (request, reply) => {
|
||||
const { id } = request.params as { id: string }
|
||||
const session = await LessonSessionService.getById(app.db, id)
|
||||
if (!session) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
||||
return reply.send(session)
|
||||
})
|
||||
|
||||
app.patch('/lesson-sessions/:id', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
||||
const { id } = request.params as { id: string }
|
||||
const parsed = LessonSessionUpdateSchema.safeParse(request.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
||||
}
|
||||
const session = await LessonSessionService.update(app.db, id, parsed.data)
|
||||
if (!session) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
||||
return reply.send(session)
|
||||
})
|
||||
|
||||
app.post('/lesson-sessions/:id/status', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
||||
const { id } = request.params as { id: string }
|
||||
const parsed = LessonSessionStatusUpdateSchema.safeParse(request.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
||||
}
|
||||
const session = await LessonSessionService.updateStatus(app.db, id, parsed.data.status)
|
||||
if (!session) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
||||
return reply.send(session)
|
||||
})
|
||||
|
||||
app.post('/lesson-sessions/:id/notes', { preHandler: [app.authenticate, app.requirePermission('lessons.edit')] }, async (request, reply) => {
|
||||
const { id } = request.params as { id: string }
|
||||
const parsed = LessonSessionNotesSchema.safeParse(request.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
||||
}
|
||||
const session = await LessonSessionService.updateNotes(app.db, id, parsed.data)
|
||||
if (!session) return reply.status(404).send({ error: { message: 'Lesson session not found', statusCode: 404 } })
|
||||
return reply.send(session)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user