Skip to content

Commit

Permalink
#2112 - Business validations: Application Bulk Withdrawal (#2465)
Browse files Browse the repository at this point in the history
## As a part of this PR, the following tasks were completed:

- [x] Checked to ensure that the affected Student Application belongs to
the institution uploading the file.
- [x] In case one or multiple validations failed, the validation errors
are returned. Below are the validation criteria:
- If the application number is not found, assuming it is an SFAS
application, it is just skipped and the rest of the file is processed. A
warning is generated to the user: "Application is not present in SIMS
and is a part of the SFAS system."
- Validated if the combination of the SIN, Application number and
institution matched with a record on SIMS. If not, returned the
following line as a warning message "The record was not found and will
be skipped."
- If the file contains a line for an application that already has a
withdrawal recorded, returned the line as a warning message: "This
application is already withdrawn with the date: [withdrawal date]."
- Verified that the application is in "Completed" status and not
archived to execute the withdrawal, if not, returned an error with the
message "The application is not in the completed status." or "The
application is already archived and cannot be withdrawn."
- [x] For every single file line that has an error or warning, a summary
is displayed:
-  Line number: record number including the header in the count.
- Application Number (even if invalid): displayed to show which
"Application Number" was interrupted.
- Withdrawal Date (even if invalid): displayed to show the "Withdrawal
Date" of the interrupted "Application Number".
- Validations: displayed to show a user-friendly message about why the
record is not considered valid.
- [x] Centralized the error validation code to the `validation-utils`
file.

### Screenshots [validation errors and warnings]:

<img width="1919" alt="image"
src="https://github.com/bcgov/SIMS/assets/7859295/d1c586fd-0e23-4d65-b8ba-472ae146e29f">


------------------------------------------------------------------------------------------------------------------------------------------

<img width="1919" alt="image"
src="https://github.com/bcgov/SIMS/assets/7859295/e47a9861-ce5e-499f-9676-37044ee232c5">


------------------------------------------------------------------------------------------------------------------------------------------

<img width="1918" alt="image"
src="https://github.com/bcgov/SIMS/assets/7859295/00a65201-a240-4337-a5fd-c7ca8febcdc9">


------------------------------------------------------------------------------------------------------------------------------------------

<img width="1920" alt="image"
src="https://github.com/bcgov/SIMS/assets/7859295/7f176c57-cde9-4ab9-9b83-ddff9ea7325f">


------------------------------------------------------------------------------------------------------------------------------------------

<img width="1920" alt="image"
src="https://github.com/bcgov/SIMS/assets/7859295/880628ff-3479-46a9-9947-5db8c958c1b0">


------------------------------------------------------------------------------------------------------------------------------------------

<img width="1919" alt="image"
src="https://github.com/bcgov/SIMS/assets/7859295/82f89661-b0eb-4fa6-9bc5-64ebaa37dd31">
  • Loading branch information
sh16011993 authored Nov 8, 2023
1 parent 4dde03d commit df59fdb
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 171 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
StudentAppealRequestsService,
ApplicationOfferingChangeRequestService,
ApplicationWithdrawalImportTextService,
ApplicationBulkWithdrawalImportValidationService,
} from "./services";
import {
ApplicationControllerService,
Expand Down Expand Up @@ -166,6 +167,7 @@ import {
ApplicationOfferingChangeRequestService,
ApplicationOfferingChangeRequestControllerService,
ApplicationWithdrawalImportTextService,
ApplicationBulkWithdrawalImportValidationService,
],
})
export class AppInstitutionsModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,8 @@ export const APPLICATION_WITHDRAWAL_TEXT_CONTENT_FORMAT_ERROR =
*/
export const APPLICATION_WITHDRAWAL_INVALID_TEXT_FILE_ERROR =
"APPLICATION_WITHDRAWAL_INVALID_TEXT_FILE_ERROR";
/**
* One or more application withdrawal business validation errors have occurred.
*/
export const APPLICATION_WITHDRAWAL_VALIDATION_ERROR =
"APPLICATION_WITHDRAWAL_VALIDATION_ERROR";
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { Allow, IsEnum, IsIn, IsNotEmpty, MaxLength } from "class-validator";
import { Type } from "class-transformer";
import {
OfferingDeliveryOptions,
OfferingValidationInfos,
OfferingValidationWarnings,
WILComponentOptions,
} from "../../../services";

Expand Down Expand Up @@ -250,6 +248,6 @@ export enum OfferingSummaryPurpose {
* or info.
*/
class ValidationResultAPIOutDTO {
typeCode: OfferingValidationWarnings | OfferingValidationInfos;
typeCode: string;
message: string;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { StudentScholasticStandingsService } from "../../services";
import { ScholasticStandingSubmittedDetailsAPIOutDTO } from "./models/student-scholastic-standings.dto";
import {
BadRequestException,
Injectable,
NotFoundException,
} from "@nestjs/common";
import {
ApplicationWithdrawalTextValidationResult,
StudentScholasticStandingsService,
} from "../../services";
import {
ApplicationBulkWithdrawalValidationResultAPIOutDTO,
ScholasticStandingSubmittedDetailsAPIOutDTO,
} from "./models/student-scholastic-standings.dto";
import { transformToActiveApplicationDataAPIOutDTO } from "../institution-locations/models/application.dto";
import { ApplicationWithdrawalValidationResult } from "../../services/application-bulk-withdrawal/application-bulk-withdrawal-validation.models";
import { ApiProcessError } from "../../types";
import {
APPLICATION_WITHDRAWAL_TEXT_CONTENT_FORMAT_ERROR,
APPLICATION_WITHDRAWAL_VALIDATION_ERROR,
} from "../../constants";

/**
* Scholastic standing controller service.
Expand Down Expand Up @@ -40,4 +56,69 @@ export class ScholasticStandingControllerService {
...transformToActiveApplicationDataAPIOutDTO(application, offering),
};
}

/**
* Verify if all text file validations were performed with success and throw
* a BadRequestException in case of some failure.
* @param textValidations validations to be verified.
*/
assertTextValidationsAreValid(
textValidations: ApplicationWithdrawalTextValidationResult[],
) {
const textValidationsErrors = textValidations.filter(
(textValidation) => textValidation.errors.length,
);
if (textValidationsErrors.length) {
// At least one error was detected and the text must be fixed.
const validationResults: ApplicationBulkWithdrawalValidationResultAPIOutDTO[] =
textValidationsErrors.map((validation) => ({
recordIndex: validation.index,
applicationNumber: validation.textModel.applicationNumber,
withdrawalDate: validation.textModel.withdrawalDate,
errors: validation.errors,
infos: [],
warnings: [],
}));
throw new BadRequestException(
new ApiProcessError(
"One or more text data fields received are not in the correct format.",
APPLICATION_WITHDRAWAL_TEXT_CONTENT_FORMAT_ERROR,
validationResults,
),
);
}
}

/**
* Verify if all application withdrawal validations were performed with success and throw
* a BadRequestException in case of some failure.
* @param validationsResult validation results to be verified.
*/
assertValidationsAreValid(
validationsResult: ApplicationWithdrawalValidationResult[],
) {
const validations = validationsResult.filter(
(validationResult) =>
validationResult.errors.length || validationResult.warnings.length,
);
if (validations.length) {
// At least one error or warning was detected.
const errorValidationResults: ApplicationBulkWithdrawalValidationResultAPIOutDTO[] =
validations.map((validation) => ({
recordIndex: validation.index,
applicationNumber: validation.validationModel.applicationNumber,
withdrawalDate: validation.validationModel.withdrawalDate,
errors: validation.errors,
warnings: validation.warnings,
infos: [],
}));
throw new BadRequestException(
new ApiProcessError(
"One or more fields have validation errors.",
APPLICATION_WITHDRAWAL_VALIDATION_ERROR,
errorValidationResults,
),
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ import {
APPLICATION_NOT_FOUND,
ASSESSMENT_ALREADY_IN_PROGRESS,
ApplicationWithdrawalImportTextService,
ApplicationBulkWithdrawalImportValidationService,
FormService,
INVALID_OPERATION_IN_THE_CURRENT_STATUS,
StudentScholasticStandingsService,
BulkWithdrawalFileData,
} from "../../services";
import { ApiProcessError, ClientTypeBaseRoute } from "../../types";
import { CustomNamedError } from "@sims/utilities";
Expand All @@ -43,7 +45,6 @@ import { FormNames } from "../../services/form/constants";
import {
APPLICATION_CHANGE_NOT_ELIGIBLE,
APPLICATION_WITHDRAWAL_INVALID_TEXT_FILE_ERROR,
APPLICATION_WITHDRAWAL_TEXT_CONTENT_FORMAT_ERROR,
} from "../../constants";
import {
ApplicationBulkWithdrawalValidationResultAPIOutDTO,
Expand All @@ -61,10 +62,6 @@ import {
uploadLimits,
} from "../../utilities";
import { PrimaryIdentifierAPIOutDTO } from "../models/primary.identifier.dto";
import {
ApplicationWithdrawalImportTextModel,
ApplicationWithdrawalTextValidationResult,
} from "../../services/application-bulk-withdrawal/application-bulk-withdrawal-import-text.models";

/**
* Scholastic standing controller for institutions Client.
Expand All @@ -78,6 +75,7 @@ export class ScholasticStandingInstitutionsController extends BaseController {
private readonly studentScholasticStandingsService: StudentScholasticStandingsService,
private readonly scholasticStandingControllerService: ScholasticStandingControllerService,
private readonly applicationWithdrawalImportTextService: ApplicationWithdrawalImportTextService,
private readonly applicationWithdrawalImportValidationService: ApplicationBulkWithdrawalImportValidationService,
) {
super();
}
Expand Down Expand Up @@ -196,13 +194,13 @@ export class ScholasticStandingInstitutionsController extends BaseController {
@UploadedFile() file: Express.Multer.File,
@Query("validation-only", new DefaultValuePipe(false), ParseBoolPipe)
validationOnly: boolean,
@UserToken() userToken: IInstitutionUserToken,
): Promise<PrimaryIdentifierAPIOutDTO[]> {
// Read the entire file content.
const fileContent = file.buffer.toString();
// Convert the file raw content into text models.
let textModels: ApplicationWithdrawalImportTextModel[];
let withdrawalFileData: BulkWithdrawalFileData;
try {
textModels =
withdrawalFileData =
this.applicationWithdrawalImportTextService.readText(fileContent);
} catch (error: unknown) {
if (
Expand Down Expand Up @@ -230,12 +228,28 @@ export class ScholasticStandingInstitutionsController extends BaseController {
// Validate the text models.
const textValidations =
this.applicationWithdrawalImportTextService.validateTextModels(
textModels,
withdrawalFileData.records,
);
// Assert successful validation.
this.assertTextValidationsAreValid(textValidations);
// TODO Add business validation for application bulk withdrawal.

this.scholasticStandingControllerService.assertTextValidationsAreValid(
textValidations,
);
// Generate the validation models.
const validationModels =
await this.applicationWithdrawalImportTextService.generateValidationModels(
textValidations,
withdrawalFileData.header.originator,
userToken.authorizations.institutionId,
);
// Validate all the application bulk withdrawal models.
const applicationBulkWithdrawalValidations =
this.applicationWithdrawalImportValidationService.validateModels(
validationModels,
);
// Assert successful validation.
this.scholasticStandingControllerService.assertValidationsAreValid(
applicationBulkWithdrawalValidations,
);
if (validationOnly) {
// If the endpoint is called only to perform the validation and no error was found
// return an empty array because no record will be created.
Expand All @@ -245,36 +259,4 @@ export class ScholasticStandingInstitutionsController extends BaseController {
// TODO create a block to do database updates for Application withdrawal.
return [];
}

/**
* Verify if all text file validations were performed with success and throw
* a BadRequestException in case of some failure.
* @param textValidations validations to be verified.
*/
private assertTextValidationsAreValid(
textValidations: ApplicationWithdrawalTextValidationResult[],
) {
const textValidationsErrors = textValidations.filter(
(textValidation) => textValidation.errors.length,
);
if (textValidationsErrors.length) {
// At least one error was detected and the text must be fixed.
const validationResults: ApplicationBulkWithdrawalValidationResultAPIOutDTO[] =
textValidationsErrors.map((validation) => ({
recordIndex: validation.index,
applicationNumber: validation.textModel.applicationNumber,
withdrawalDate: validation.textModel.withdrawalDate,
errors: validation.errors,
infos: [],
warnings: [],
}));
throw new BadRequestException(
new ApiProcessError(
"One or more text data fields received are not in the correct format.",
APPLICATION_WITHDRAWAL_TEXT_CONTENT_FORMAT_ERROR,
validationResults,
),
);
}
}
}
Loading

0 comments on commit df59fdb

Please sign in to comment.