// Kodeva — Prisma schema // DEV: SQLite (tanpa Docker/Postgres). Untuk PRODUKSI ganti provider ke "postgresql" // dan ubah String enum/list kembali menjadi enum + String[] (lihat AGENTS.md §6). // // Nilai enum disimpan sebagai String agar kompatibel SQLite. Konstanta TypeScript // tersedia di src/lib/constants.ts. generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) name String email String @unique passwordHash String role String @default("TEACHER") // ADMIN | TEACHER avatarUrl String? phone String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt classesTaught Class[] @relation("ClassTeacher") sessions Session[] journals Journal[] reportCards ReportCard[] placementTests PlacementTest[] @relation("Examiner") followUpsOwned FollowUp[] @relation("FollowUpPIC") } model Student { id String @id @default(cuid()) fullName String phone String email String? @unique photoUrl String? parentName String? parentPhone String? birthDate DateTime? gender String? // MALE | FEMALE address String? leadSource String // WHATSAPP | REFERRAL | BANNER | SOCIAL_MEDIA status String @default("CALON_STUDENT") // CALON_STUDENT | STUDENT | EX_STUDENT notes String? joinedAt DateTime? archivedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt enrollments Enrollment[] attendances Attendance[] payments Payment[] reportCards ReportCard[] placementTests PlacementTest[] followUps FollowUp[] journalStudents JournalStudent[] @@index([status]) @@index([leadSource]) } model Class { id String @id @default(cuid()) name String code String @unique description String? level String // BEGINNER | INTERMEDIATE | ADVANCED teacherId String? teacher User? @relation("ClassTeacher", fields: [teacherId], references: [id]) totalMeetings Int @default(10) packagePrice Int @default(0) pricePerMeeting Int? capacity Int @default(12) schedule String? startDate DateTime? endDate DateTime? status String @default("ACTIVE") // DRAFT | ACTIVE | COMPLETED | ARCHIVED color String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt enrollments Enrollment[] sessions Session[] payments Payment[] journals Journal[] reportCards ReportCard[] } model Enrollment { id String @id @default(cuid()) studentId String student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) classId String class Class @relation(fields: [classId], references: [id], onDelete: Cascade) status String @default("ACTIVE") // ACTIVE | COMPLETED | DROPPED joinedAt DateTime @default(now()) leftAt DateTime? createdAt DateTime @default(now()) @@unique([studentId, classId]) } model Session { id String @id @default(cuid()) classId String class Class @relation(fields: [classId], references: [id], onDelete: Cascade) meetingNumber Int date DateTime topic String? teacherId String? teacher User? @relation(fields: [teacherId], references: [id]) createdAt DateTime @default(now()) attendances Attendance[] journals Journal[] @@unique([classId, meetingNumber]) } model Attendance { id String @id @default(cuid()) sessionId String session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) studentId String student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) status String // PRESENT | LATE | EXCUSED | SICK | ABSENT note String? recordedById String? createdAt DateTime @default(now()) @@unique([sessionId, studentId]) } model Payment { id String @id @default(cuid()) invoiceNumber String @unique studentId String student Student @relation(fields: [studentId], references: [id]) classId String class Class @relation(fields: [classId], references: [id]) meetingsPaid Int pricePerMeeting Int amount Int method String // CASH | TRANSFER | QRIS status String @default("PAID") // UNPAID | PARTIAL | PAID | REFUNDED paidAt DateTime note String? proofUrl String? createdById String? createdAt DateTime @default(now()) } model Journal { id String @id @default(cuid()) classId String class Class @relation(fields: [classId], references: [id], onDelete: Cascade) sessionId String? session Session? @relation(fields: [sessionId], references: [id]) title String description String? activityDate DateTime tags String @default("") // comma separated authorId String author User @relation(fields: [authorId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt photos JournalPhoto[] students JournalStudent[] } model JournalPhoto { id String @id @default(cuid()) journalId String journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade) url String caption String? sortOrder Int @default(0) } model JournalStudent { journalId String studentId String journal Journal @relation(fields: [journalId], references: [id], onDelete: Cascade) student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) @@id([journalId, studentId]) } model ReportCard { id String @id @default(cuid()) studentId String student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) classId String class Class @relation(fields: [classId], references: [id]) period String teacherId String teacher User @relation(fields: [teacherId], references: [id]) attendanceSummary String? finalScore Int? grade String? teacherNotes String? publishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt scores ReportScore[] } model ReportScore { id String @id @default(cuid()) reportCardId String reportCard ReportCard @relation(fields: [reportCardId], references: [id], onDelete: Cascade) aspect String score Int note String? } model PlacementTest { id String @id @default(cuid()) studentId String student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) testDate DateTime levelResult String // BEGINNER | INTERMEDIATE | ADVANCED recommendedClassId String? totalScore Int examinerId String? examiner User? @relation("Examiner", fields: [examinerId], references: [id]) notes String? createdAt DateTime @default(now()) scores PlacementScore[] } model PlacementScore { id String @id @default(cuid()) placementTestId String placementTest PlacementTest @relation(fields: [placementTestId], references: [id], onDelete: Cascade) category String score Int } model FollowUp { id String @id @default(cuid()) studentId String student Student @relation(fields: [studentId], references: [id], onDelete: Cascade) status String @default("NEW") // NEW | CONTACTED | TRIAL | OFFER_SENT | ENROLLED | LOST picId String? pic User? @relation("FollowUpPIC", fields: [picId], references: [id]) nextFollowUpAt DateTime? lastContactedAt DateTime? channel String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt activities FollowUpActivity[] @@index([status]) @@index([nextFollowUpAt]) } model FollowUpActivity { id String @id @default(cuid()) followUpId String followUp FollowUp @relation(fields: [followUpId], references: [id], onDelete: Cascade) type String // call | wa | email | meeting note String authorId String? createdAt DateTime @default(now()) } model Setting { key String @id value String // JSON string (SQLite) }