feat: add absence notification and analytics features

- Implemented AbsenceNotifyView for notifying absence with session details.
- Created AnalyticsView for displaying group analytics and managing students at risk.
- Added ClassAttendanceView for marking attendance in classes.
- Defined new schemas for analytics and attendance in shared package.
This commit is contained in:
Jose Selesan
2026-09-23 16:24:45 -03:00
parent a363e9e8fc
commit 2e184845e1
59 changed files with 3854 additions and 23 deletions

View File

@@ -0,0 +1,68 @@
-- CreateEnum
CREATE TYPE "AttendanceStatus" AS ENUM ('PRESENT', 'ABSENT', 'EXCUSED');
-- AlterTable
ALTER TABLE "attendees" ADD COLUMN "notifyToken" TEXT;
-- CreateTable
CREATE TABLE "class_sessions" (
"id" TEXT NOT NULL,
"groupId" TEXT NOT NULL,
"startsAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "class_sessions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "attendances" (
"id" TEXT NOT NULL,
"attendeeId" TEXT NOT NULL,
"classSessionId" TEXT NOT NULL,
"status" "AttendanceStatus" NOT NULL,
"markedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "attendances_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "slot_releases" (
"id" TEXT NOT NULL,
"attendeeId" TEXT NOT NULL,
"classSessionId" TEXT NOT NULL,
"isClaimed" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "slot_releases_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "class_sessions_groupId_startsAt_key" ON "class_sessions"("groupId", "startsAt");
-- CreateIndex
CREATE UNIQUE INDEX "attendances_attendeeId_classSessionId_key" ON "attendances"("attendeeId", "classSessionId");
-- CreateIndex
CREATE UNIQUE INDEX "slot_releases_attendeeId_classSessionId_key" ON "slot_releases"("attendeeId", "classSessionId");
-- CreateIndex
CREATE UNIQUE INDEX "attendees_notifyToken_key" ON "attendees"("notifyToken");
-- AddForeignKey
ALTER TABLE "class_sessions" ADD CONSTRAINT "class_sessions_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "attendances" ADD CONSTRAINT "attendances_attendeeId_fkey" FOREIGN KEY ("attendeeId") REFERENCES "attendees"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "attendances" ADD CONSTRAINT "attendances_classSessionId_fkey" FOREIGN KEY ("classSessionId") REFERENCES "class_sessions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "slot_releases" ADD CONSTRAINT "slot_releases_attendeeId_fkey" FOREIGN KEY ("attendeeId") REFERENCES "attendees"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "slot_releases" ADD CONSTRAINT "slot_releases_classSessionId_fkey" FOREIGN KEY ("classSessionId") REFERENCES "class_sessions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Backfill: tokens de aviso de ausencia para alumnos existentes
UPDATE "attendees" SET "notifyToken" = gen_random_uuid()::text WHERE "notifyToken" IS NULL;

View File

@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "AttendeeStatus" AS ENUM ('ACTIVE', 'PAUSED', 'DROPPED');
-- AlterTable
ALTER TABLE "attendees" ADD COLUMN "status" "AttendeeStatus" NOT NULL DEFAULT 'ACTIVE';

View File

@@ -0,0 +1,50 @@
// Attendance & class sessions
enum AttendanceStatus {
PRESENT
ABSENT
EXCUSED
}
model ClassSession {
id String @id @default(cuid())
groupId String
startsAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
attendances Attendance[]
slotReleases SlotRelease[]
@@unique([groupId, startsAt])
@@map("class_sessions")
}
model Attendance {
id String @id @default(cuid())
attendeeId String
classSessionId String
status AttendanceStatus
markedAt DateTime @default(now())
attendee Attendee @relation(fields: [attendeeId], references: [id], onDelete: Cascade)
classSession ClassSession @relation(fields: [classSessionId], references: [id], onDelete: Cascade)
@@unique([attendeeId, classSessionId])
@@map("attendances")
}
model SlotRelease {
id String @id @default(cuid())
attendeeId String
classSessionId String
isClaimed Boolean @default(false)
createdAt DateTime @default(now())
attendee Attendee @relation(fields: [attendeeId], references: [id], onDelete: Cascade)
classSession ClassSession @relation(fields: [classSessionId], references: [id], onDelete: Cascade)
@@unique([attendeeId, classSessionId])
@@map("slot_releases")
}

View File

@@ -20,6 +20,7 @@ model Group {
attendees Attendee[]
payments Payment[]
waitlist GroupWaitlistEntry[]
classSessions ClassSession[]
@@map("groups")
}
@@ -59,8 +60,14 @@ enum Role {
MEMBER
}
enum AttendeeStatus {
ACTIVE
PAUSED
DROPPED
}
model Attendee {
id String @id @default(cuid())
id String @id @default(cuid())
groupId String
fullName String
email String?
@@ -68,11 +75,15 @@ model Attendee {
guardianName String?
guardianPhone String?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
notifyToken String? @unique @default(cuid())
status AttendeeStatus @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
payments Payment[]
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
payments Payment[]
attendances Attendance[]
slotReleases SlotRelease[]
@@index([groupId])
@@map("attendees")