From f7e819d6a34eb6db06d54d4695efa33a0dbad698 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan <54600590+dheepak-aot@users.noreply.github.com> Date: Tue, 27 Jun 2023 17:04:07 -0600 Subject: [PATCH] #2033 - Validating FT and PT offering minimum length (#2041) Validating FT and PT offering minimum length --- ...s-valid-offering-period-for-funded-days.ts | 31 ++++-- ...tion-program-offering-validation.models.ts | 18 +++- .../custom-validators/period-min-length.ts | 28 ++++-- .../system-configurations-constants.ts | 10 +- .../educationprogramoffering.json | 99 +++++++++++++++---- 5 files changed, 148 insertions(+), 38 deletions(-) diff --git a/sources/packages/backend/apps/api/src/services/education-program-offering/custom-validators/has-valid-offering-period-for-funded-days.ts b/sources/packages/backend/apps/api/src/services/education-program-offering/custom-validators/has-valid-offering-period-for-funded-days.ts index 70c530675d..0e1df4d363 100644 --- a/sources/packages/backend/apps/api/src/services/education-program-offering/custom-validators/has-valid-offering-period-for-funded-days.ts +++ b/sources/packages/backend/apps/api/src/services/education-program-offering/custom-validators/has-valid-offering-period-for-funded-days.ts @@ -5,10 +5,7 @@ import { ValidationOptions, ValidationArguments, } from "class-validator"; -import { - OFFERING_STUDY_PERIOD_MAX_DAYS, - OFFERING_STUDY_PERIOD_MIN_DAYS, -} from "../../../utilities"; +import { OFFERING_STUDY_PERIOD_MAX_DAYS } from "../../../utilities"; import { StudyBreak } from "../education-program-offering-validation.models"; import { OfferingCalculationValidationBaseConstraint } from "./offering-calculation-validation-base-constraint"; @@ -23,20 +20,32 @@ class HasValidOfferingPeriodForFundedDaysConstraint implements ValidatorConstraintInterface { validate(studyBreaks: StudyBreak[], args: ValidationArguments): boolean { + const offeringMinDaysAllowedValue = this.getOfferingMinAllowedDays(args); const calculatedStudyBreaksAndWeeks = this.getCalculatedStudyBreaks( studyBreaks, args, ); return ( calculatedStudyBreaksAndWeeks.fundedStudyPeriodDays >= - OFFERING_STUDY_PERIOD_MIN_DAYS && + offeringMinDaysAllowedValue && calculatedStudyBreaksAndWeeks.fundedStudyPeriodDays <= OFFERING_STUDY_PERIOD_MAX_DAYS ); } - defaultMessage() { - return `The funded study amount of days is ineligible for StudentAid BC funding. Your dates must be between ${OFFERING_STUDY_PERIOD_MIN_DAYS} to ${OFFERING_STUDY_PERIOD_MAX_DAYS} days.`; + defaultMessage(args: ValidationArguments) { + const offeringMinDaysAllowedValue = this.getOfferingMinAllowedDays(args); + return `The funded study amount of days is ineligible for StudentAid BC funding. Your dates must be between ${offeringMinDaysAllowedValue} to ${OFFERING_STUDY_PERIOD_MAX_DAYS} days.`; + } + + /** + * Get offering minimum allowed days from args. + * @param args validation arguments. + * @returns minimum allowed days. + */ + private getOfferingMinAllowedDays(args: ValidationArguments): number { + const [, , offeringMinDaysAllowed] = args.constraints; + return offeringMinDaysAllowed(args.object) as number; } } @@ -46,12 +55,14 @@ class HasValidOfferingPeriodForFundedDaysConstraint * allowed study period amount of days. * @param startPeriodProperty property of the model that identifies the offering start date. * @param endPeriodProperty property of the model that identifies the offering end date. + * @param offeringMinDaysAllowed study period minimum length in number of days. * @param validationOptions validations options. * @returns true if the study period is valid, otherwise, false. */ export function HasValidOfferingPeriodForFundedDays( startPeriodProperty: (targetObject: unknown) => Date | string, endPeriodProperty: (targetObject: unknown) => Date | string, + offeringMinDaysAllowed: (targetObject: unknown) => number, validationOptions?: ValidationOptions, ) { return (object: unknown, propertyName: string) => { @@ -60,7 +71,11 @@ export function HasValidOfferingPeriodForFundedDays( target: object.constructor, propertyName, options: validationOptions, - constraints: [startPeriodProperty, endPeriodProperty], + constraints: [ + startPeriodProperty, + endPeriodProperty, + offeringMinDaysAllowed, + ], validator: HasValidOfferingPeriodForFundedDaysConstraint, }); }; diff --git a/sources/packages/backend/apps/api/src/services/education-program-offering/education-program-offering-validation.models.ts b/sources/packages/backend/apps/api/src/services/education-program-offering/education-program-offering-validation.models.ts index f56071bb5a..9465175cfd 100644 --- a/sources/packages/backend/apps/api/src/services/education-program-offering/education-program-offering-validation.models.ts +++ b/sources/packages/backend/apps/api/src/services/education-program-offering/education-program-offering-validation.models.ts @@ -46,7 +46,8 @@ import { OFFERING_STUDY_BREAK_MAX_DAYS, OFFERING_STUDY_BREAK_MIN_DAYS, OFFERING_STUDY_PERIOD_MAX_DAYS, - OFFERING_STUDY_PERIOD_MIN_DAYS, + OFFERING_STUDY_PERIOD_MIN_DAYS_FULL_TIME, + OFFERING_STUDY_PERIOD_MIN_DAYS_PART_TIME, OFFERING_YEAR_OF_STUDY_MAX_VALUE, OFFERING_YEAR_OF_STUDY_MIN_VALUE, } from "../../utilities"; @@ -319,6 +320,18 @@ const studyStartDateProperty = (offering: OfferingValidationModel) => const studyEndDateProperty = (offering: OfferingValidationModel) => offering.studyEndDate; +/** + * Get study period minimum length based on offering intensity. + * @param offering offering. + * @returns minimum study period length in number of days. + */ +const studyPeriodMinLength = (offering: OfferingValidationModel) => { + if (offering.offeringIntensity === OfferingIntensity.fullTime) { + return OFFERING_STUDY_PERIOD_MIN_DAYS_FULL_TIME; + } + return OFFERING_STUDY_PERIOD_MIN_DAYS_PART_TIME; +}; + /** * Complete offering data with all validations needed to ensure data * consistency. Program data and locations data need to be present to @@ -353,7 +366,7 @@ export class OfferingValidationModel { @IsDateAfter(studyStartDateProperty, userFriendlyNames.studyEndDate) @PeriodMinLength( studyStartDateProperty, - OFFERING_STUDY_PERIOD_MIN_DAYS, + studyPeriodMinLength, userFriendlyNames.studyEndDate, { context: ValidationContext.CreateWarning( @@ -595,6 +608,7 @@ export class OfferingValidationModel { @HasValidOfferingPeriodForFundedDays( studyStartDateProperty, studyEndDateProperty, + studyPeriodMinLength, { context: ValidationContext.CreateWarning( OfferingValidationWarnings.InvalidStudyDatesPeriodLength, diff --git a/sources/packages/backend/apps/api/src/utilities/class-validation/custom-validators/period-min-length.ts b/sources/packages/backend/apps/api/src/utilities/class-validation/custom-validators/period-min-length.ts index ef217b02c9..a2c683284a 100644 --- a/sources/packages/backend/apps/api/src/utilities/class-validation/custom-validators/period-min-length.ts +++ b/sources/packages/backend/apps/api/src/utilities/class-validation/custom-validators/period-min-length.ts @@ -15,23 +15,38 @@ import { dateDifference, getDateOnlyFormat } from "@sims/utilities"; @ValidatorConstraint() class PeriodMinLengthConstraint implements ValidatorConstraintInterface { validate(value: Date | string, args: ValidationArguments): boolean { - const [startDateProperty, minDaysAllowed] = args.constraints; + const [startDateProperty] = args.constraints; + const minDaysAllowedValue = this.getMinAllowedDays(args); const periodStartDate = startDateProperty(args.object); if (!periodStartDate) { // The related property does not exists in the provided object to be compared. return false; } - return dateDifference(value, periodStartDate) >= minDaysAllowed; + return dateDifference(value, periodStartDate) >= minDaysAllowedValue; } defaultMessage(args: ValidationArguments) { - const [startDateProperty, minDaysAllowed, propertyDisplayName] = - args.constraints; + const [startDateProperty, , propertyDisplayName] = args.constraints; const startDate = getDateOnlyFormat(startDateProperty(args.object)); const endDate = getDateOnlyFormat(args.value); + const minDaysAllowedValue = this.getMinAllowedDays(args); return `${ propertyDisplayName ?? args.property - }, the number of day(s) between ${startDate} and ${endDate} must be at least ${minDaysAllowed}.`; + }, the number of day(s) between ${startDate} and ${endDate} must be at least ${minDaysAllowedValue}.`; + } + + /** + * Get minimum allowed days from args. + * @param args validation arguments. + * @returns minimum allowed days. + */ + private getMinAllowedDays(args: ValidationArguments): number { + const [, minDaysAllowed] = args.constraints; + const minDaysAllowedValue = + minDaysAllowed instanceof Function + ? minDaysAllowed(args.object) + : minDaysAllowed; + return minDaysAllowedValue as number; } } @@ -42,6 +57,7 @@ class PeriodMinLengthConstraint implements ValidatorConstraintInterface { * @param startDateProperty indicates the property that define the * start of a period. * @param minDaysAllowed min allowed days to the period be considered valid. + * This could be a number or a function that returns a value of minimum allowed days. * @param propertyDisplayName user-friendly property name to be added to the * validation message. * @param validationOptions validations options. @@ -49,7 +65,7 @@ class PeriodMinLengthConstraint implements ValidatorConstraintInterface { */ export function PeriodMinLength( startDateProperty: (targetObject: unknown) => Date | string, - minDaysAllowed: number, + minDaysAllowed: ((targetObject: unknown) => number) | number, propertyDisplayName?: string, validationOptions?: ValidationOptions, ) { diff --git a/sources/packages/backend/apps/api/src/utilities/system-configurations-constants.ts b/sources/packages/backend/apps/api/src/utilities/system-configurations-constants.ts index c335e60576..1456b8a28d 100644 --- a/sources/packages/backend/apps/api/src/utilities/system-configurations-constants.ts +++ b/sources/packages/backend/apps/api/src/utilities/system-configurations-constants.ts @@ -60,9 +60,15 @@ export const OFFERING_COURSE_LOAD_MIN_VALUE = 20; */ export const OFFERING_COURSE_LOAD_MAX_VALUE = 59; /** - * Minimum amount of days to an offering study period. + * Minimum amount of days required for a full time offering study period. */ -export const OFFERING_STUDY_PERIOD_MIN_DAYS = 42; +export const OFFERING_STUDY_PERIOD_MIN_DAYS_FULL_TIME = 84; + +/** + * Minimum amount of days required for a part time offering study period. + */ +export const OFFERING_STUDY_PERIOD_MIN_DAYS_PART_TIME = 42; + /** * Maximum amount of days to an offering study period. */ diff --git a/sources/packages/forms/src/form-definitions/educationprogramoffering.json b/sources/packages/forms/src/form-definitions/educationprogramoffering.json index 73ea35b9dd..dd27c392ed 100644 --- a/sources/packages/forms/src/form-definitions/educationprogramoffering.json +++ b/sources/packages/forms/src/form-definitions/educationprogramoffering.json @@ -1,9 +1,9 @@ { "title": "Education Program Offering", - "display": "form", - "type": "form", "name": "educationprogramoffering", "path": "educationprogramoffering", + "type": "form", + "display": "form", "tags": [ "common" ], @@ -170,7 +170,8 @@ "showWordCount": false, "allowMultipleMasks": false, "addons": [], - "id": "ec8hh1" + "id": "ec8hh1", + "isNew": false }, { "label": "HTML", @@ -836,7 +837,8 @@ "inputType": "radio", "fieldSet": false, "id": "epwmfzr", - "defaultValue": "" + "defaultValue": "", + "lockKey": true }, { "label": "Study period intensity popup", @@ -1022,7 +1024,8 @@ "allowMultipleMasks": false, "addons": [], "id": "emuvfb", - "defaultValue": "" + "defaultValue": "", + "inputType": "number" } ], "placeholder": "", @@ -2085,7 +2088,7 @@ "value": "" } ], - "content": "This study period is ineligible for StudentAid BC funding\n
\nYour dates must be between 42 to 365 days.", + "content": "This study period is ineligible for StudentAid BC funding\n
\nYour dates must be between {{data.studyPeriodMinDays}} to {{data.studyPeriodMaxDays}} days.", "refreshOnChange": true, "customClass": "banner-warning ", "hidden": false, @@ -2094,7 +2097,7 @@ "tags": [], "properties": {}, "conditional": { - "show": null, + "show": "", "when": null, "eq": "", "json": "" @@ -2153,7 +2156,8 @@ "showWordCount": false, "allowMultipleMasks": false, "addons": [], - "id": "ebhbfou" + "id": "ebhbfou", + "isNew": false } ], "placeholder": "", @@ -2375,7 +2379,8 @@ "allowMultipleMasks": false, "addons": [], "tag": "p", - "id": "eity25i" + "id": "eity25i", + "className": "" }, { "label": "No study breaks", @@ -2650,7 +2655,8 @@ "addons": [], "inputType": "text", "id": "eufdf4l0000000", - "defaultValue": "" + "defaultValue": "", + "inDataGrid": true }, { "label": "Break end date", @@ -2760,7 +2766,8 @@ "addons": [], "inputType": "text", "id": "endqveo000000", - "defaultValue": "" + "defaultValue": "", + "inDataGrid": true }, { "label": "Total break days", @@ -2837,7 +2844,9 @@ "showWordCount": false, "properties": {}, "allowMultipleMasks": false, - "addons": [] + "addons": [], + "inDataGrid": true, + "inputType": "number" }, { "label": "Total eligible break days", @@ -2913,7 +2922,9 @@ "showWordCount": false, "properties": {}, "allowMultipleMasks": false, - "addons": [] + "addons": [], + "inDataGrid": true, + "inputType": "number" }, { "label": "Total ineligible break days", @@ -2989,7 +3000,9 @@ "showWordCount": false, "properties": {}, "allowMultipleMasks": false, - "addons": [] + "addons": [], + "inDataGrid": true, + "inputType": "number" } ], "placeholder": "", @@ -4169,7 +4182,8 @@ "allowMultipleMasks": false, "addons": [], "tag": "p", - "id": "e6zws5q" + "id": "e6zws5q", + "className": "" }, { "collapsible": false, @@ -4264,7 +4278,8 @@ "properties": {}, "allowMultipleMasks": false, "addons": [], - "id": "ey5wqhj" + "id": "ey5wqhj", + "inputType": "number" } ], "width": 6, @@ -4355,7 +4370,8 @@ "properties": {}, "allowMultipleMasks": false, "addons": [], - "id": "ezezla" + "id": "ezezla", + "inputType": "number" } ], "width": 6, @@ -4520,7 +4536,8 @@ "properties": {}, "allowMultipleMasks": false, "addons": [], - "id": "eont0uf" + "id": "eont0uf", + "inputType": "number" } ], "width": 6, @@ -4611,7 +4628,8 @@ "properties": {}, "allowMultipleMasks": false, "addons": [], - "id": "esc41a" + "id": "esc41a", + "inputType": "number" } ], "width": 6, @@ -5184,7 +5202,8 @@ "allowMultipleMasks": false, "addons": [], "tag": "p", - "id": "e7678z" + "id": "e7678z", + "className": "" }, { "label": "I confirm this study period offering meets the policies outlined in the StudentAid BC policy manual.", @@ -5725,6 +5744,46 @@ "addons": [], "inputType": "hidden", "id": "ejidpjh" + }, + { + "input": true, + "tableView": true, + "key": "studyPeriodMinDays", + "label": "Study period minimum days", + "protected": false, + "unique": false, + "persistent": true, + "type": "hidden", + "tags": [], + "conditional": { + "show": "", + "when": null, + "eq": "" + }, + "properties": {}, + "lockKey": true, + "customDefaultValue": "", + "calculateValue": "value = data.offeringIntensity === 'Full Time' ? 84 : 42;" + }, + { + "input": true, + "tableView": true, + "key": "studyPeriodMaxDays", + "label": "Study period maximum days", + "protected": false, + "unique": false, + "persistent": true, + "type": "hidden", + "tags": [], + "conditional": { + "show": "", + "when": null, + "eq": "" + }, + "properties": {}, + "defaultValue": "365", + "lockKey": true, + "isNew": false } ] } \ No newline at end of file