All files intake-cohort.service.ts

100% Statements 77/77
100% Branches 21/21
100% Functions 6/6
100% Lines 77/77

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105        1x 1x                             1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x   1x 17x 17x 17x 17x 17x 17x 17x 17x 12x 17x   1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x   1x 8x 7x 7x 7x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x   1x   5x   4x 4x 4x 5x   3x 3x 3x 3x 3x 5x   1x 4x 4x 4x 4x   3x 3x 3x 3x 3x 4x 1x  
/**
 * S-6 Phase-1: Tenant-scoped intake cohort service.
 * An intake cohort groups students who begin the same program together.
 */
import { getPrisma } from "../lib/prisma-context.js";
import { ApiError } from "../utils/api-error.js";
 
export interface CreateCohortInput {
  universityId: string;
  programTypeId?: string | null;
  startDate: Date;
  name: string;
}
 
export interface UpdateCohortInput {
  programTypeId?: string | null;
  startDate?: Date;
  name?: string;
}
 
export class IntakeCohortService {
  static async list(universityId: string) {
    return getPrisma().intakeCohort.findMany({
      where: { universityId },
      orderBy: [{ startDate: "desc" }, { name: "asc" }],
      include: {
        programType: { select: { id: true, name: true, type: true } },
        _count: { select: { students: true } },
      },
    });
  }
 
  static async getById(universityId: string, id: string) {
    const cohort = await getPrisma().intakeCohort.findFirst({
      where: { id, universityId },
      include: {
        programType: { select: { id: true, name: true, type: true } },
        _count: { select: { students: true } },
      },
    });
    if (!cohort) throw ApiError.notFound("Intake cohort not found");
    return cohort;
  }
 
  static async create(input: CreateCohortInput) {
    return getPrisma().intakeCohort.create({
      data: {
        universityId: input.universityId,
        programTypeId: input.programTypeId ?? null,
        startDate: input.startDate,
        name: input.name,
      },
      include: {
        programType: { select: { id: true, name: true, type: true } },
        _count: { select: { students: true } },
      },
    });
  }
 
  static async update(universityId: string, id: string, input: UpdateCohortInput) {
    const existing = await this.getById(universityId, id);
    return getPrisma().intakeCohort.update({
      where: { id: existing.id },
      data: {
        ...(input.name !== undefined ? { name: input.name } : {}),
        ...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
        ...(input.programTypeId !== undefined ? { programTypeId: input.programTypeId } : {}),
      },
      include: {
        programType: { select: { id: true, name: true, type: true } },
        _count: { select: { students: true } },
      },
    });
  }
 
  static async assignStudent(universityId: string, cohortId: string, studentId: string) {
    // Verify cohort belongs to tenant
    await this.getById(universityId, cohortId);
 
    const student = await getPrisma().student.findFirst({
      where: { id: studentId, universityId },
    });
    if (!student) throw ApiError.notFound("Student not found");
 
    return getPrisma().student.update({
      where: { id: studentId },
      data: { intakeCohortId: cohortId },
      select: { id: true, name: true, intakeCohortId: true },
    });
  }
 
  static async unassignStudent(universityId: string, studentId: string) {
    const student = await getPrisma().student.findFirst({
      where: { id: studentId, universityId },
    });
    if (!student) throw ApiError.notFound("Student not found");
 
    return getPrisma().student.update({
      where: { id: studentId },
      data: { intakeCohortId: null },
      select: { id: true, name: true, intakeCohortId: true },
    });
  }
}