Add lessons Phase 6: lesson plans with curriculum tracking

Structured lesson plans with nested sections and items per enrollment.
Deep create in one request, one-active-per-enrollment constraint,
auto-set startedDate/masteredDate on status transitions, progress %
calculation (skipped items excluded). 8 new tests (84 total).
This commit is contained in:
Ryan Moon
2026-03-30 09:40:41 -05:00
parent 31f661ff4f
commit aae5a022a8
8 changed files with 661 additions and 2 deletions

View File

@@ -118,6 +118,10 @@ export {
LessonSessionUpdateSchema,
GradingScaleCreateSchema,
GradingScaleUpdateSchema,
LessonPlanItemStatus,
LessonPlanCreateSchema,
LessonPlanUpdateSchema,
LessonPlanItemUpdateSchema,
} from './lessons.schema.js'
export type {
InstructorCreateInput,
@@ -134,4 +138,7 @@ export type {
LessonSessionUpdateInput,
GradingScaleCreateInput,
GradingScaleUpdateInput,
LessonPlanCreateInput,
LessonPlanUpdateInput,
LessonPlanItemUpdateInput,
} from './lessons.schema.js'

View File

@@ -131,3 +131,52 @@ export const GradingScaleUpdateSchema = z.object({
isDefault: z.boolean().optional(),
})
export type GradingScaleUpdateInput = z.infer<typeof GradingScaleUpdateSchema>
// --- Lesson Plan schemas ---
export const LessonPlanItemStatus = z.enum(['not_started', 'in_progress', 'mastered', 'skipped'])
export type LessonPlanItemStatus = z.infer<typeof LessonPlanItemStatus>
const LessonPlanItemInput = z.object({
title: z.string().min(1).max(255),
description: opt(z.string()),
gradingScaleId: opt(z.string().uuid()),
targetGradeValue: opt(z.string().max(50)),
sortOrder: z.coerce.number().int(),
})
const LessonPlanSectionInput = z.object({
title: z.string().min(1).max(255),
description: opt(z.string()),
sortOrder: z.coerce.number().int(),
items: z.array(LessonPlanItemInput).default([]),
})
export const LessonPlanCreateSchema = z.object({
memberId: z.string().uuid(),
enrollmentId: z.string().uuid(),
title: z.string().min(1).max(255),
description: opt(z.string()),
startedDate: opt(z.string()),
sections: z.array(LessonPlanSectionInput).default([]),
})
export type LessonPlanCreateInput = z.infer<typeof LessonPlanCreateSchema>
export const LessonPlanUpdateSchema = z.object({
title: z.string().min(1).max(255).optional(),
description: opt(z.string()),
isActive: z.boolean().optional(),
})
export type LessonPlanUpdateInput = z.infer<typeof LessonPlanUpdateSchema>
export const LessonPlanItemUpdateSchema = z.object({
title: z.string().min(1).max(255).optional(),
description: opt(z.string()),
status: LessonPlanItemStatus.optional(),
gradingScaleId: opt(z.string().uuid()),
currentGradeValue: opt(z.string().max(50)),
targetGradeValue: opt(z.string().max(50)),
notes: opt(z.string()),
sortOrder: z.coerce.number().int().optional(),
})
export type LessonPlanItemUpdateInput = z.infer<typeof LessonPlanItemUpdateSchema>