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

@@ -143,6 +143,61 @@ export const gradingScaleLevels = pgTable('grading_scale_level', {
sortOrder: integer('sort_order').notNull(),
})
export const lessonPlanItemStatusEnum = pgEnum('lesson_plan_item_status', [
'not_started',
'in_progress',
'mastered',
'skipped',
])
export const memberLessonPlans = pgTable('member_lesson_plan', {
id: uuid('id').primaryKey().defaultRandom(),
memberId: uuid('member_id')
.notNull()
.references(() => members.id),
enrollmentId: uuid('enrollment_id')
.notNull()
.references(() => enrollments.id),
createdBy: uuid('created_by').references(() => users.id),
title: varchar('title', { length: 255 }).notNull(),
description: text('description'),
isActive: boolean('is_active').notNull().default(true),
startedDate: date('started_date'),
completedDate: date('completed_date'),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
})
export const lessonPlanSections = pgTable('lesson_plan_section', {
id: uuid('id').primaryKey().defaultRandom(),
lessonPlanId: uuid('lesson_plan_id')
.notNull()
.references(() => memberLessonPlans.id),
title: varchar('title', { length: 255 }).notNull(),
description: text('description'),
sortOrder: integer('sort_order').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
})
export const lessonPlanItems = pgTable('lesson_plan_item', {
id: uuid('id').primaryKey().defaultRandom(),
sectionId: uuid('section_id')
.notNull()
.references(() => lessonPlanSections.id),
title: varchar('title', { length: 255 }).notNull(),
description: text('description'),
status: lessonPlanItemStatusEnum('status').notNull().default('not_started'),
gradingScaleId: uuid('grading_scale_id').references(() => gradingScales.id),
currentGradeValue: varchar('current_grade_value', { length: 50 }),
targetGradeValue: varchar('target_grade_value', { length: 50 }),
startedDate: date('started_date'),
masteredDate: date('mastered_date'),
notes: text('notes'),
sortOrder: integer('sort_order').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
})
// --- Type exports ---
export type Instructor = typeof instructors.$inferSelect
@@ -159,3 +214,9 @@ export type GradingScale = typeof gradingScales.$inferSelect
export type GradingScaleInsert = typeof gradingScales.$inferInsert
export type GradingScaleLevel = typeof gradingScaleLevels.$inferSelect
export type GradingScaleLevelInsert = typeof gradingScaleLevels.$inferInsert
export type MemberLessonPlan = typeof memberLessonPlans.$inferSelect
export type MemberLessonPlanInsert = typeof memberLessonPlans.$inferInsert
export type LessonPlanSection = typeof lessonPlanSections.$inferSelect
export type LessonPlanSectionInsert = typeof lessonPlanSections.$inferInsert
export type LessonPlanItem = typeof lessonPlanItems.$inferSelect
export type LessonPlanItemInsert = typeof lessonPlanItems.$inferInsert