#2987 - Institution prevent remittance - Enable location only restrictions#5791
Conversation
There was a problem hiding this comment.
Pull request overview
Enables “location-only” (and more flexible) institution restrictions by introducing restriction metadata-driven field requirements, updating the DB schema to allow nullable program/location links, and propagating the new contract through the API, UI, and tests.
Changes:
- Added restriction
metadata.fieldRequirements(andFieldRequirementType) to drive which fields are required/optional/not allowed per restriction reason. - Updated institution restriction persistence to allow
program_id/location_idto be nullable, including updated uniqueness constraints and a new REMIT restriction seed. - Updated AEST restriction endpoints + web UI modal to consume field requirements, with expanded e2e coverage.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| sources/packages/web/src/types/contracts/RestrictionContract.ts | Adds FieldRequirementType enum to support metadata-driven UI validation. |
| sources/packages/web/src/services/http/dto/Restriction.dto.ts | Introduces RestrictionAPIOutDTO to return reasons with field requirements. |
| sources/packages/web/src/services/http/RestrictionApi.ts | Makes category optional and updates return type for restriction reasons. |
| sources/packages/web/src/services/RestrictionService.ts | Aligns service return types with new reasons DTO. |
| sources/packages/web/src/components/institutions/modals/AddRestrictionModal.vue | Shows/hides program/location fields and applies rules based on fieldRequirements. |
| sources/packages/backend/libs/test-utils/src/models/common.model.ts | Adds REMIT to RestrictionCode for test support. |
| sources/packages/backend/libs/test-utils/src/factories/restriction.ts | Allows creating fake restrictions with metadata. |
| sources/packages/backend/libs/sims-db/src/entities/restriction.type.ts | Adds RestrictionMetadata typing for restriction metadata. |
| sources/packages/backend/libs/sims-db/src/entities/restriction.model.ts | Adds nullable metadata JSONB column to Restriction entity. |
| sources/packages/backend/libs/sims-db/src/entities/institution-restriction.model.ts | Makes program/location relations nullable to support flexible applicability. |
| sources/packages/backend/libs/sims-db/src/entities/index.ts | Exports new common types and adjusts export ordering. |
| sources/packages/backend/libs/sims-db/src/entities/common.type.ts | Defines backend FieldRequirementType enum. |
| sources/packages/backend/libs/integrations/src/services/disbursement-schedule/disbursement-schedule.models.ts | Makes institution restriction program/location optional and adjusts effective restriction filtering. |
| sources/packages/backend/apps/db-migrations/src/sql/Restrictions/Rollback-institution-restrictions-drop-program-location-not-null.sql | Rollback for nullable program/location and unique index changes. |
| sources/packages/backend/apps/db-migrations/src/sql/Restrictions/Rollback-insert-remit-restriction.sql | Rollback removal of REMIT restriction seed. |
| sources/packages/backend/apps/db-migrations/src/sql/Restrictions/Rollback-add-col-metadata-and-update-sus.sql | Rollback for restrictions metadata column. |
| sources/packages/backend/apps/db-migrations/src/sql/Restrictions/Institution-restrictions-drop-program-location-not-null.sql | Drops NOT NULL constraints and recreates unique index with NULL-aware semantics. |
| sources/packages/backend/apps/db-migrations/src/sql/Restrictions/Insert-remit-restriction.sql | Seeds new REMIT restriction with metadata-based field requirements. |
| sources/packages/backend/apps/db-migrations/src/sql/Restrictions/Add-col-metadata-and-update-sus.sql | Adds metadata column and populates SUS metadata. |
| sources/packages/backend/apps/db-migrations/src/migrations/1771391174102-InstitutionRestrictionDropProgramLocationNotNull.ts | TypeORM migration wrapper for institution restriction nullability/index changes. |
| sources/packages/backend/apps/db-migrations/src/migrations/1771299851366-RestrictionAddColMetadataUpdateSUSInsertREMIT.ts | TypeORM migration wrapper for metadata + SUS update + REMIT insert. |
| sources/packages/backend/apps/api/src/utilities/metadata-utils.ts | Adds reusable validation for metadata-driven field requirements. |
| sources/packages/backend/apps/api/src/utilities/index.ts | Re-exports metadata utilities. |
| sources/packages/backend/apps/api/src/services/restriction/restriction.service.ts | Returns restriction metadata when fetching reasons. |
| sources/packages/backend/apps/api/src/services/restriction/models/institution-restriction.model.ts | Adds models for institution restriction creation/validation. |
| sources/packages/backend/apps/api/src/services/restriction/institution-restriction.service.ts | Supports optional program/location restrictions, metadata validation, and null joins. |
| sources/packages/backend/apps/api/src/route-controllers/restriction/restriction.aest.controller.ts | Returns fieldRequirements on reasons and maps new validation errors to 400. |
| sources/packages/backend/apps/api/src/route-controllers/restriction/models/restriction.dto.ts | Makes request filtering optional and adds RestrictionAPIOutDTO for reasons. |
| sources/packages/backend/apps/api/src/route-controllers/restriction/tests/e2e/restriction.aest.controller.getReasonsOptionsList.e2e-spec.ts | Expands coverage for reasons list with/without category and metadata. |
| sources/packages/backend/apps/api/src/route-controllers/restriction/tests/e2e/restriction.aest.controller.addInstitutionRestriction.e2e-spec.ts | Adds e2e coverage for SUS, REMIT, and institution-only restriction creation + validation failures. |
| sources/packages/backend/apps/api/src/constants/error-code.constants.ts | Adds FIELD_REQUIREMENTS_NOT_VALID error code. |
Comments suppressed due to low confidence (1)
sources/packages/backend/libs/integrations/src/services/disbursement-schedule/disbursement-schedule.models.ts:422
getEffectiveInstitutionRestrictionsnow uses optional chaining comparisons (restriction.program?.id === programIdandrestriction.location?.id === locationId), which will exclude any institution restrictions that intentionally haveprogramorlocationunset (e.g., the new location-only restrictions whereprogramis not allowed). This likely prevents REMIT/location-only and institution-level restrictions from ever being applied during e-Cert/disbursement calculations. Consider treatingnull/undefinedprogram/location on a restriction as a wildcard (match all) and only enforcing equality when the field is present.
private getEffectiveInstitutionRestrictions(): ReadonlyArray<InstitutionActiveRestriction> {
const programId = this.offering.educationProgram.id;
const locationId = this.offering.institutionLocation.id;
return this.institutionRestrictions.filter(
(restriction) =>
restriction.program?.id === programId &&
restriction.location?.id === locationId &&
!this.institutionRestrictionsBypassedIds.includes(
restriction.institutionRestrictionId,
),
);
sources/packages/backend/apps/api/src/route-controllers/restriction/models/restriction.dto.ts
Outdated
Show resolved
Hide resolved
sources/packages/backend/apps/api/src/route-controllers/restriction/models/restriction.dto.ts
Outdated
Show resolved
Hide resolved
...s/packages/backend/apps/api/src/services/restriction/models/institution-restriction.model.ts
Outdated
Show resolved
Hide resolved
sources/packages/backend/apps/api/src/services/restriction/institution-restriction.service.ts
Show resolved
Hide resolved
| const selectedReason = reasons.value.find( | ||
| (reason) => reason.id === formModel.restrictionId, | ||
| ); | ||
| if (!selectedReason?.fieldRequirements) { | ||
| return { canShow: false }; | ||
| } | ||
| const fieldRequirement = selectedReason.fieldRequirements[fieldKey]; | ||
| const canShow = fieldRequirement !== FieldRequirementType.NotAllowed; |
There was a problem hiding this comment.
When the selected restriction changes, programId/locationIds values from a previously selected reason can remain in formModel even if the new reason marks the field as NotAllowed (the input gets hidden but the value is still submitted). This can cause backend validation errors that the user cannot fix because the field is no longer visible. Consider watching formModel.restrictionId (or programAttributes/locationAttributes) and clearing formModel.programId/formModel.locationIds when canShow becomes false (and possibly when a field becomes optional/required).
.../db-migrations/src/migrations/1771299851366-RestrictionAddColMetadataUpdateSUSInsertREMIT.ts
Show resolved
Hide resolved
sources/packages/backend/libs/sims-db/src/entities/restriction.type.ts
Outdated
Show resolved
Hide resolved
| restrictionId: number, | ||
| locationIds: number[], | ||
| programId: number, | ||
| noteDescription: string, |
There was a problem hiding this comment.
Please review the parameter comments.
sources/packages/backend/apps/api/src/services/restriction/institution-restriction.service.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
metadata-utils.ts seems too generic. Can it be prefixed with "restrictions"?
sources/packages/backend/apps/api/src/services/restriction/institution-restriction.service.ts
Outdated
Show resolved
Hide resolved
| .expect(HttpStatus.BAD_REQUEST) | ||
| .expect({ | ||
| message: | ||
| "Field requirement error(s): program is required, location is required.", |
There was a problem hiding this comment.
Really great to see the details in the validations and the validation messages ❤️
...ollers/restriction/_tests_/e2e/restriction.aest.controller.getReasonsOptionsList.e2e-spec.ts
Show resolved
Hide resolved
andrewsignori-aot
left a comment
There was a problem hiding this comment.
Great work introducing the restrictions metadata framework. Please take a look at the minor comments.
| async getReasonsOptionsList( | ||
| @Query() options: RestrictionReasonsOptionsAPIInDTO, | ||
| ): Promise<OptionItemAPIOutDTO[]> { | ||
| ): Promise<RestrictionAPIOutDTO[]> { |
There was a problem hiding this comment.
it did caught my attention that get "reasons" is returning a list of restriction objects
There was a problem hiding this comment.
Calling the API as reasons was not accurate, as it returns restriction details. This endpoint requires refactor and has a ticket for the same. I took the opportunity to update the DTO name alone 😊
|
andrewsignori-aot
left a comment
There was a problem hiding this comment.
Thanks for making the changes, looks good 👍



Institution prevent remittance - Enable location only restrictions
DB Migrations
sims.restrictionsand updated the metadata for SUS retriction.Metadata framework
sims.restrictionshas a propertyfieldRequirementsto evaluate the field requirements of the restricted party(Student restriction | Institution restriction).sims.institution_restrictions. The program and location validations are governed by restriction metadata now.institution_id_location_id_program_id_restriction_id_is_active_uniqueto consider NULL as value for uniqueness for the existing combination ofinstitution_id,location_id,program_id and restriction_idRollback evidence
API
UI
E2E Tests