File

src/database/entities/ratedEntity.entity.ts

Extends

SubExerciseParams

Index

Properties

Properties

subexercises
subexercises: SubExercise[]
Type : SubExercise[]
import { Embeddable, Embedded, PrimaryKey, Property } from '@mikro-orm/core';
import { ExercisePointsInfo, IExercisePointsInfo } from 'shared/model/Gradings';
import { IExercise, ISubexercise } from 'shared/model/HasExercises';
import { v4 } from 'uuid';
import { HasExercisesDTO, RatedEntityDTO } from '../../module/scheinexam/scheinexam.dto';
import { ExerciseDTO, SubExerciseDTO } from '../../module/sheet/sheet.dto';
import { Student } from './student.entity';
import { GradingList } from '../../helpers/GradingList';

@Embeddable()
export class SubExercise {
    @Property()
    id: string;

    @Property()
    exerciseName: string;

    @Property()
    bonus: boolean;

    @Property()
    maxPoints: number;

    constructor(params: SubExerciseParams) {
        this.exerciseName = params.exerciseName;
        this.bonus = params.bonus;
        this.maxPoints = params.maxPoints;
        this.id = params.id ?? v4();
    }

    /**
     * @returns Total points of this exercise split by must-have and bonus points.
     */
    get pointInfo(): ExercisePointsInfo {
        return new ExercisePointsInfo({
            must: this.bonus ? 0 : this.maxPoints,
            bonus: this.bonus ? this.maxPoints : 0,
        });
    }

    static fromDTO(dto: SubExerciseDTO): SubExercise {
        return new SubExercise({
            id: dto.id,
            exerciseName: dto.exName,
            bonus: dto.bonus,
            maxPoints: dto.maxPoints,
        });
    }

    toDTO(): ISubexercise {
        return {
            id: this.id,
            exName: this.exerciseName,
            bonus: this.bonus,
            maxPoints: this.maxPoints,
        };
    }
}

@Embeddable()
export class Exercise extends SubExercise {
    // You might ask: "Why do I need a prefix for a column name here? The objects are part of a JSON in a completely different column"
    // The answer is simple: If you don't provide a prefix the embedded objects of an embeddable are saved as empty objects, because Mikro-ORM somehow (silently) overrides it's own keys pointing to the entries...
    @Embedded({ entity: () => SubExercise, array: true, prefix: 'sub_' })
    subexercises: SubExercise[] = [];

    constructor(params: ExerciseParams) {
        super(params);
        this.subexercises = [...params.subexercises];
    }

    /**
     * The total points of the exercise not caring about bonus (sub)exercises.
     *
     * @returns Total points of the exercise.
     */
    get totalPoints(): number {
        if (this.subexercises.length > 0) {
            return this.subexercises.reduce((sum, current) => sum + current.maxPoints, 0);
        } else {
            return this.maxPoints;
        }
    }

    /**
     * Returns the total points of this exercise split by must-have and bonus points.
     *
     * Takes subexercises into the account.
     *
     * @returns Total points of this exercise split by must-have and bonus points.
     */
    get pointInfo(): ExercisePointsInfo {
        if (this.subexercises.length === 0) {
            return super.pointInfo;
        }

        const info: IExercisePointsInfo = this.subexercises.reduce(
            (prev, current) =>
                current.bonus
                    ? { ...prev, bonus: current.maxPoints + prev.bonus }
                    : { ...prev, must: current.maxPoints + prev.must },
            { must: 0, bonus: 0 }
        );
        return new ExercisePointsInfo(info);
    }

    static fromDTO(dto: ExerciseDTO): Exercise {
        return new Exercise({
            id: dto.id,
            exerciseName: dto.exName,
            bonus: dto.bonus,
            maxPoints: dto.maxPoints,
            subexercises: dto.subexercises?.map((subExDto) => SubExercise.fromDTO(subExDto)) ?? [],
        });
    }

    toDTO(): IExercise {
        return {
            id: this.id,
            bonus: this.bonus,
            exName: this.exerciseName,
            maxPoints: this.totalPoints,
            subexercises: this.subexercises.map((subEx) => subEx.toDTO()),
        };
    }
}

export abstract class HasExercises implements HandIn {
    @PrimaryKey()
    id = v4();

    @Embedded(() => Exercise, { array: true })
    exercises: Exercise[] = [];

    protected constructor(params: HasExercisesParams) {
        this.exercises = [...params.exercises];
    }

    /**
     * @returns Total points of all exercises of the hand-in split by must-have and bonus points.
     */
    get totalPoints(): ExercisePointsInfo {
        const info: IExercisePointsInfo = this.exercises.reduce(
            (sum, current) => {
                const ptInfoEx = current.pointInfo;
                return {
                    must: sum.must + ptInfoEx.must,
                    bonus: sum.bonus + ptInfoEx.bonus,
                };
            },
            { must: 0, bonus: 0 }
        );
        return new ExercisePointsInfo(info);
    }

    updateFromDTO(dto: HasExercisesDTO): void {
        this.exercises = dto.exercises.map((exDto) => Exercise.fromDTO(exDto));
    }
}

export abstract class RatedEntity extends HasExercises {
    // TODO: Replace with FloatType in MikroORM v5
    @Property({ type: 'float' })
    percentageNeeded: number;

    protected constructor(params: RatedEntityParams) {
        super(params);
        this.percentageNeeded = params.percentageNeeded;
    }

    /**
     * @param student Student to check
     * @returns `True` if the student passes this rated entity.
     */
    hasPassed(student: Student, gradings: GradingList): boolean {
        return this.getPassedInformation(student, gradings).passed;
    }

    /**
     * Returns the `PassedInformation` for the given student.
     *
     * Those information contain:
     * - Has the student passed?
     * - How many points does the student achieve?
     * - How many must-have points has this entity.
     *
     * @param student Student to get the `PassedInformation` of.
     * @returns The `PassedInformation` of the given student related to the entity.
     */
    getPassedInformation(student: Student, gradings: GradingList): PassedInformation {
        const total = this.totalPoints;
        const grading = gradings.getGradingOfHandIn(this);

        if (!grading) {
            return { passed: false, achieved: 0, total };
        }

        const achieved = grading.points;
        return {
            passed: achieved / total.must >= this.percentageNeeded,
            achieved,
            total,
        };
    }

    updateFromDTO(dto: RatedEntityDTO): void {
        super.updateFromDTO(dto);
        this.percentageNeeded = dto.percentageNeeded;
    }
}

export interface HandIn {
    id: string;
    exercises: Exercise[];
}

export interface HasExercisesParams {
    exercises: Exercise[];
}

export interface RatedEntityParams extends HasExercisesParams {
    percentageNeeded: number;
}

interface SubExerciseParams {
    id?: string;
    exerciseName: string;
    bonus: boolean;
    maxPoints: number;
}

interface ExerciseParams extends SubExerciseParams {
    subexercises: SubExercise[];
}

interface PassedInformation {
    passed: boolean;
    achieved: number;
    total: ExercisePointsInfo;
}

results matching ""

    No results matching ""