File
Description
Per-course enrollment summary — one entry per learning_group_participant row
for a participant. Used by the View Details dialog to render every course
with its own stage pill, progress, and meta info.
stage carries the literal Prisma enum value (PENDING_INVITE, INVITED,
ACCEPTED, PRE_BAT, E_LEARNING, POST_BAT, COMPLETED). Cancelled enrollments
are excluded server-side and won't appear here.
|
last_active_at
|
last_active_at: string | null
|
Type : string | null
|
import { Exclude, Expose, Transform, TransformationType } from "class-transformer";
import { getFullName, getStatus } from "../utils";
/**
* Per-course enrollment summary — one entry per learning_group_participant row
* for a participant. Used by the View Details dialog to render every course
* with its own stage pill, progress, and meta info.
*
* `stage` carries the literal Prisma enum value (PENDING_INVITE, INVITED,
* ACCEPTED, PRE_BAT, E_LEARNING, POST_BAT, COMPLETED). Cancelled enrollments
* are excluded server-side and won't appear here.
*/
export interface CLParticipantEnrolledCourse {
enrollment_id: number;
learning_group_id: number;
course_name: string;
stage: string;
progress: number;
allocated_at: string;
last_active_at: string | null;
}
@Exclude()
export class CLParticipantDto {
@Expose()
id!: number;
/**
* Only derive from DB row on PLAIN_TO_CLASS (plainToInstance). On CLASS_TO_PLAIN (HTTP serializer)
* `obj` is this DTO instance, which has no first_name/last_name — re-running would yield "Unknown".
*/
@Expose()
@Transform(({ obj, value, type }) =>
type === TransformationType.PLAIN_TO_CLASS ? getFullName(obj) : value,
)
full_name!: string;
@Expose()
email!: string;
@Expose()
department!: string;
@Expose()
@Transform(({ obj, value, type }) =>
type === TransformationType.PLAIN_TO_CLASS ? getStatus(obj) : value,
)
status!: string;
@Expose()
last_active!: string;
@Expose()
progress!: number;
@Expose()
courses_completed!: number;
@Expose()
total_courses!: number;
@Expose()
current_course?: string;
// Email subscription status (optional - only included when requested)
@Expose()
email_subscription_status?: {
isUnsubscribed: boolean;
unsubscribedAt: string | null;
unsubscribedReason: string | null;
resubscribedAt: string | null;
resubscribeCount: number;
};
// === Invitation tracking fields — populated by the participants list service ===
/**
* Stage of the participant's MOST RECENTLY allocated course assignment.
* One of: PENDING_INVITE, INVITED, ACCEPTED, PRE_BAT, E_LEARNING, POST_BAT, COMPLETED.
* Drives the row's stage pill in the client portal directory.
*/
@Expose()
current_stage?: string;
/**
* True when the participant has set up a password (users.password_hash IS NOT NULL).
* Distinguishes a brand-new INVITED user from a returning user whose new course
* just sits at ACCEPTED awaiting course-acceptance.
*/
@Expose()
has_password?: boolean;
/**
* True when the most recently allocated course has reached e-Learning Done
* (status >= E_LEARNING_COMPLETED on the backend's progress timeline).
* Drives visibility of the "Allocate next course" menu item.
*/
@Expose()
eligible_for_next?: boolean;
/**
* All non-cancelled course enrollments for this participant. Populated for
* the list endpoint so the frontend can render the per-course breakdown
* inside the View Details dialog without an extra round-trip.
*/
@Expose()
enrolled_courses?: CLParticipantEnrolledCourse[];
}