From 1fa691c13c3f23593e79784fb6d670ee09fa4808 Mon Sep 17 00:00:00 2001 From: Natalie Clamp Date: Wed, 8 Jan 2025 16:10:02 +0000 Subject: [PATCH 1/5] SAA-2272: Initial API set up --- README.md | 3 +- feature.env | 1 + generate-alerts-types.sh | 1 + helm_deploy/values-dev.yaml | 1 + helm_deploy/values-preprod.yaml | 1 + helm_deploy/values-prod.yaml | 1 + integration_tests/e2e/health.cy.ts | 1 + server/@types/alertsAPI/index.d.ts | 2343 +++++++++++++++++++++++++ server/@types/alertsAPI/types.ts | 3 + server/config.ts | 8 + server/data/alertsApiClient.test.ts | 42 + server/data/alertsApiClient.ts | 20 + server/data/index.test.ts | 4 +- server/data/index.ts | 2 + server/services/alertsService.test.ts | 58 + server/services/alertsService.ts | 11 + server/services/healthCheck.ts | 1 + server/services/index.test.ts | 4 +- server/services/index.ts | 3 + 19 files changed, 2505 insertions(+), 3 deletions(-) create mode 100644 generate-alerts-types.sh create mode 100644 server/@types/alertsAPI/index.d.ts create mode 100644 server/@types/alertsAPI/types.ts create mode 100644 server/data/alertsApiClient.test.ts create mode 100644 server/data/alertsApiClient.ts create mode 100644 server/services/alertsService.test.ts create mode 100644 server/services/alertsService.ts diff --git a/README.md b/README.md index 80e7a30fd..7ef705e3a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This service requires the following dependent services: ## Alerts -- Details of relevant alerts can be obtained from the `hmpps-prodiner-profile` repository [here](https://github.com/ministryofjustice/hmpps-prisoner-profile/blob/main/server/data/alertFlags/alertFlags.ts). +- Details of relevant alerts can be obtained from the `hmpps-prisoner-profile` repository [here](https://github.com/ministryofjustice/hmpps-prisoner-profile/blob/main/server/data/alertFlags/alertFlags.ts). - The icons for badges can be obtained from the `digital-prison-services` repository [here](https://github.com/ministryofjustice/digital-prison-services/tree/main/static/images). ## Running the application @@ -100,6 +100,7 @@ FRONTEND_COMPONENT_API_URL=https://frontend-components-dev.hmpps.service.justice MANAGE_USERS_API_URL=https://manage-users-api-dev.hmpps.service.justice.gov.uk BOOK_A_VIDEO_LINK_API_URL=https://book-a-video-link-api-dev.prison.service.justice.gov.uk NON_ASSOCIATIONS_API_URL=https://non-associations-api-dev.hmpps.service.justice.gov.uk +ALERTS_API_URL=https://alerts-api-dev.hmpps.service.justice.gov.uk LOCATIONS_INSIDE_PRISON_API_URL=https://locations-inside-prison-api-dev.hmpps.service.justice.gov.uk NOMIS_MAPPING_API_URL=https://nomis-sync-prisoner-mapping-dev.hmpps.service.justice.gov.uk VIDEO_CONFERENCE_SCHEDULE_URL=https://video-conference-schedule-dev.prison.service.justice.gov.uk diff --git a/feature.env b/feature.env index 10b9e6886..35f3b4bfa 100644 --- a/feature.env +++ b/feature.env @@ -11,6 +11,7 @@ MANAGE_USERS_API_URL=http://localhost:9091 CASE_NOTES_API_URL=http://localhost:9091 BOOK_A_VIDEO_LINK_API_URL=http://localhost:9091 NON_ASSOCIATIONS_API_URL=http://localhost:9091 +ALERTS_API_URL=http://localhost:9091 LOCATIONS_INSIDE_PRISON_API_URL=http://localhost:9091 NOMIS_MAPPING_API_URL=http://localhost:9091 TOKEN_VERIFICATION_ENABLED=true diff --git a/generate-alerts-types.sh b/generate-alerts-types.sh new file mode 100644 index 000000000..c6ccd83d5 --- /dev/null +++ b/generate-alerts-types.sh @@ -0,0 +1 @@ +npx openapi-typescript https://alerts-api-dev.hmpps.service.justice.gov.uk/v3/api-docs > server/@types/alertsApi/index.d.ts \ No newline at end of file diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index 909fd435f..1d181719a 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -24,6 +24,7 @@ generic-service: MANAGE_USERS_API_URL: "https://manage-users-api-dev.hmpps.service.justice.gov.uk" BOOK_A_VIDEO_LINK_API_URL: "https://book-a-video-link-api-dev.prison.service.justice.gov.uk" NON_ASSOCIATIONS_API_URL: "https://non-associations-api-dev.hmpps.service.justice.gov.uk" + ALERTS_API_URL: "https://alerts-api-dev.hmpps.service.justice.gov.uk" REPORTING_API_URL: "http://hmpps-digital-prison-reporting-mi-dev.hmpps-digital-prison-reporting-mi-dev.svc.cluster.local" LOCATIONS_INSIDE_PRISON_API_URL: "https://locations-inside-prison-api-dev.hmpps.service.justice.gov.uk" NOMIS_MAPPING_API_URL: "https://nomis-sync-prisoner-mapping-dev.hmpps.service.justice.gov.uk" diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index 1a3e84288..8cfb281f1 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -24,6 +24,7 @@ generic-service: MANAGE_USERS_API_URL: "https://manage-users-api-preprod.hmpps.service.justice.gov.uk" BOOK_A_VIDEO_LINK_API_URL: "https://book-a-video-link-api-preprod.prison.service.justice.gov.uk" NON_ASSOCIATIONS_API_URL: "https://non-associations-api-preprod.hmpps.service.justice.gov.uk" + ALERTS_API_URL: "https://alerts-api-preprod.hmpps.service.justice.gov.uk" REPORTING_API_URL: "http://hmpps-digital-prison-reporting-mi-preprod.hmpps-digital-prison-reporting-mi-preprod.svc.cluster.local" LOCATIONS_INSIDE_PRISON_API_URL: "https://locations-inside-prison-api-preprod.hmpps.service.justice.gov.uk" NOMIS_MAPPING_API_URL: "https://nomis-sync-prisoner-mapping-preprod.hmpps.service.justice.gov.uk" diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index 0075cf563..502d64a95 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -22,6 +22,7 @@ generic-service: MANAGE_USERS_API_URL: "https://manage-users-api.hmpps.service.justice.gov.uk" BOOK_A_VIDEO_LINK_API_URL: "https://book-a-video-link-api.prison.service.justice.gov.uk" NON_ASSOCIATIONS_API_URL: "https://non-associations-api.hmpps.service.justice.gov.uk" + ALERTS_API_URL: "https://alerts-api.hmpps.service.justice.gov.uk" REPORTING_API_URL: "http://hmpps-digital-prison-reporting-mi-prod.hmpps-digital-prison-reporting-mi-prod.svc.cluster.local" LOCATIONS_INSIDE_PRISON_API_URL: "https://locations-inside-prison-api.hmpps.service.justice.gov.uk" NOMIS_MAPPING_API_URL: "https://nomis-sync-prisoner-mapping.hmpps.service.justice.gov.uk" diff --git a/integration_tests/e2e/health.cy.ts b/integration_tests/e2e/health.cy.ts index 5c395fd7e..ea5ea94ae 100644 --- a/integration_tests/e2e/health.cy.ts +++ b/integration_tests/e2e/health.cy.ts @@ -37,6 +37,7 @@ context('Healthcheck', () => { expect(response.body.checks.manageUsersApi).to.equal('OK') expect(response.body.checks.bookAVideoLinkApi).to.equal('OK') expect(response.body.checks.nonAssociationsApi).to.equal('OK') + expect(response.body.checks.alertsApi).to.equal('OK') expect(response.body.checks.locationsInsidePrisonApi).to.equal('OK') expect(response.body.checks.nomisMapping).to.equal('OK') expect(response.body.checks.tokenVerification).to.contain({ status: 500, retries: 2 }) diff --git a/server/@types/alertsAPI/index.d.ts b/server/@types/alertsAPI/index.d.ts new file mode 100644 index 000000000..89ff3fd17 --- /dev/null +++ b/server/@types/alertsAPI/index.d.ts @@ -0,0 +1,2343 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + '/alerts/{alertUuid}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Get an alert by its unique identifier + * @description Returns the alert with the matching identifier. + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RO + * * ROLE_PRISONER_ALERTS__RW + */ + get: operations['retrieveAlert'] + /** + * Update an alert + * @description + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RW + */ + put: operations['updateAlert'] + post?: never + /** + * Delete an alert + * @description This endpoint fully removes the alert from the system. It is used when an alert has been created in error or should otherwise be removed from this service. This endpoint will return 200 OK if the alert was not found or already deleted. This is to prevent low value warnings being logged. + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RW + */ + delete: operations['deleteAlert'] + options?: never + head?: never + patch?: never + trace?: never + } + '/search/alerts/prison-numbers': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * Gets all the alerts for prisoners by their prison numbers + * @description Returns all the alerts for the supplied prison numbers. + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RO + * * ROLE_PRISONER_ALERTS__RW + */ + post: operations['retrievePrisonerAlerts'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/resync/{prisonNumber}/alerts': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * Resync all alerts for a prisoner from NOMIS + * @description + * + * Requires one of the following roles: + * * ROLE_NOMIS_ALERTS + */ + post: operations['resyncPrisonerAlerts'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/prisoners/{prisonNumber}/alerts': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Gets all the alerts for a prisoner by their prison number + * @description + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RO + * * ROLE_PRISONER_ALERTS__RW + */ + get: operations['retrievePrisonerAlerts_1'] + put?: never + /** + * Create an alert + * @description + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RW + */ + post: operations['createPrisonerAlert'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/bulk-alerts/plan': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * Create the plan for bulk alerts + * @description + * + * + */ + post: operations['createPlan'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/bulk-alerts/plan/{id}/start': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * Start the plan for bulk alerts + * @description + * + * + */ + post: operations['startPlan'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/alert-types': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Get all alert types + * @description Returns the full list of alert types and the alert codes within those types. By default this endpoint only returns active alert types and codes. The include inactive parameter can be used to return all alert types and codes. + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RO + * * ROLE_PRISONER_ALERTS__RW + */ + get: operations['retrieveAlertTypes'] + put?: never + /** + * Create an alert type + * @description Create a new alert type, typically from the Alerts UI + * + * + */ + post: operations['createAlertType'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/alert-codes': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Retrieve all alert codes + * @description Retrieve all alert codes, typically from the Alerts UI + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RO + * * ROLE_PRISONER_ALERTS__RW + */ + get: operations['retrieveAlertCodes'] + put?: never + /** + * Create an alert code + * @description Create a new alert code, typically from the Alerts UI + * + * + */ + post: operations['createAlertCode'] + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/bulk-alerts/plan/{id}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + delete?: never + options?: never + head?: never + /** + * Update the plan for bulk alerts + * @description + * + * + */ + patch: operations['updatePlan'] + trace?: never + } + '/alert-types/{alertType}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + delete?: never + options?: never + head?: never + /** + * Update alert type + * @description Set the properties of an alert type to the submitted value. + * + * + */ + patch: operations['updateAlertType'] + trace?: never + } + '/alert-types/{alertType}/reactivate': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + delete?: never + options?: never + head?: never + /** + * Reactivate an alert type + * @description Reactivate an alert type, typically from the Alerts UI + * + * + */ + patch: operations['reactivateAlertType'] + trace?: never + } + '/alert-types/{alertType}/deactivate': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + delete?: never + options?: never + head?: never + /** + * Deactivate an alert type + * @description Deactivate an alert type, typically from the Alerts UI + * + * + */ + patch: operations['deactivateAlertType'] + trace?: never + } + '/alert-codes/{alertCode}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Retrieve an alert code + * @description Retrieve an alert code, typically from the Alerts UI + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RO + * * ROLE_PRISONER_ALERTS__RW + */ + get: operations['retrieveAlertCode'] + put?: never + post?: never + delete?: never + options?: never + head?: never + /** + * Update alert code + * @description Set the properties of an alert code to the submitted value. + * + * + */ + patch: operations['updateAlertCode'] + trace?: never + } + '/alert-codes/{alertCode}/reactivate': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + delete?: never + options?: never + head?: never + /** + * Reactivate an alert code + * @description Reactivate an alert code, typically from the Alerts UI + * + * + */ + patch: operations['reactivateAlertCode'] + trace?: never + } + '/alert-codes/{alertCode}/deactivate': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + post?: never + delete?: never + options?: never + head?: never + /** + * Deactivate an alert code + * @description Deactivate an alert code, typically from the Alerts UI + * + * + */ + patch: operations['deactivateAlertCode'] + trace?: never + } + '/bulk-alerts/plan/{id}/status': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Get the status of a plan + * @description + * + * + */ + get: operations['getPlanStatus'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/bulk-alerts/plan/{id}/prisoners': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Get prisoners associated with a plan + * @description + * + * + */ + get: operations['getPlanPrisoners'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/bulk-alerts/plan/{id}/affects': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Get counts associated with a plan + * @description + * + * + */ + get: operations['getPlanAffect'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } + '/alert-types/{alertTypeCode}': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** + * Get an alert type + * @description Returns the specified alert type. + * + * Requires one of the following roles: + * * ROLE_PRISONER_ALERTS__RO + * * ROLE_PRISONER_ALERTS__RW + */ + get: operations['retrieveAlertType'] + put?: never + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } +} +export type webhooks = Record +export interface components { + schemas: { + /** @description The alert data to use to update an alert in the service */ + UpdateAlert: { + /** + * @description The updated description of the alert. Will be ignored if null and will clear the description if empty. This is a free text field and can be used to provide additional information about the alert e.g. the reasons for adding it.It is limited to 4000 characters. + * @example Alert description + */ + description?: string + /** + * @description The updated user, staff member, approving person or organisation that authorised the alert to be added. Will be ignored if null and will clear the authorised by value if empty. This is a free text field and can be used to record the name of the person who authorised the alert. It is limited to 40 characters. + * @example A. Nurse, An Agency + */ + authorisedBy?: string + /** + * Format: date + * @description The date the alert should be active from. If set to null the field will be ignoredThe active from date can be in the past or the future, but must be before the active to date + * @example 2021-09-27 + */ + activeFrom?: string + /** + * Format: date + * @description The date the alert should be active until. If set to null i.e. cleared, the alert will be active indefinitely. The active to date can be in the past or the future, but must be after the active from date + * @example 2022-07-15 + */ + activeTo?: string + } + ErrorResponse: { + /** Format: int32 */ + status: number + errorCode?: string + userMessage?: string + developerMessage?: string + moreInfo?: string + } + /** @description An alert associated with a person */ + Alert: { + /** + * Format: uuid + * @description The unique identifier assigned to the alert + * @example 8cdadcf3-b003-4116-9956-c99bd8df6a00 + */ + alertUuid: string + /** + * @description The prison number of the person the alert is for. Also referred to as the offender number, offender id or NOMS id. + * @example A1234AA + */ + prisonNumber: string + /** @description The alert code for the alert. A person will only have one alert using each code active at any one time. */ + alertCode: components['schemas']['AlertCodeSummary'] + /** + * @description The description of the alert. It is a free text field and is used to provide additional information about the alert e.g. the reasons for adding it.It is limited to 4000 characters. + * @example Alert description + */ + description?: string + /** + * @description The user, staff member, approving person or organisation that authorised the alert to be added. It is a free text field and is used to record the name of the person who authorised the alert. It is limited to 40 characters. + * @example A. Nurse, An Agency + */ + authorisedBy?: string + /** + * Format: date + * @description The date the alert should be active from. If not provided, the alert will be active from the current date. The active from date can be in the past or the future, but must be before the active to date + * @example 2021-09-27 + */ + activeFrom: string + /** + * Format: date + * @description The date the alert should be active until. If not provided, the alert will be active indefinitely. The active to date can be in the past or the future, but must be after the active from date + * @example 2022-07-15 + */ + activeTo?: string + /** + * @description Indicates that the alert is active for the person. Alerts are active if their active from date is in the past and their active to date is either null or in the future. Note that this field is read only and cannot be set directly using the API. + * @example true + */ + isActive: boolean + /** + * Format: date-time + * @description The date and time the alert was created + * @example 2021-09-27T14:19:25 + */ + createdAt: string + /** + * @description The username of the user who created the alert + * @example USER1234 + */ + createdBy: string + /** + * @description The displayable name of the user who created the alert + * @example Firstname Lastname + */ + createdByDisplayName: string + /** + * Format: date-time + * @description The date and time the alert was last modified + * @example 2022-07-15T15:24:56 + */ + lastModifiedAt?: string + /** + * @description The username of the user who last modified the alert + * @example USER1234 + */ + lastModifiedBy?: string + /** + * @description The displayable name of the user who last modified the alert + * @example Firstname Lastname + */ + lastModifiedByDisplayName?: string + /** + * Format: date-time + * @description The date and time the alert was last set to expire. + * @example 2022-07-15T15:24:56 + */ + activeToLastSetAt?: string + /** + * @description The username of the user who last set the alert to expire. + * @example USER1234 + */ + activeToLastSetBy?: string + /** + * @description The displayable name of the user who last set the alert to expire. + * @example Firstname Lastname + */ + activeToLastSetByDisplayName?: string + } + /** @description The summary information of an alert code used to categorise alerts */ + AlertCodeSummary: { + /** + * @description The short code for the alert type + * @example A + */ + alertTypeCode: string + /** + * @description The description of the alert type + * @example Alert type description + */ + alertTypeDescription: string + /** + * @description The short code for the alert code. Usually starts with the alert type code + * @example ABC + */ + code: string + /** + * @description The description of the alert code + * @example Alert code description + */ + description: string + } + AlertsResponse: { + content: components['schemas']['Alert'][] + } + ResyncAlert: { + /** + * Format: int64 + * @description The internal NOMIS id for the offender booking. An alert in NOMIS is uniquely identified by the offender booking id and alert sequence.This is returned as part of the migrated alert response for mapping between NOMIS and DPS. + * @example 12345 + */ + offenderBookId: number + /** + * Format: int32 + * @description The NOMIS alert sequence. An alert in NOMIS is uniquely identified by the offender booking id and alert sequence.This is returned as part of the migrated alert response for mapping between NOMIS and DPS. + * @example 2 + */ + alertSeq: number + /** + * @description The alert code for the alert. A person should only have one alert using each code active at any one time however this is not enforced during migration. The alert code must exist but can be inactive. + * @example ABC + */ + alertCode: string + /** + * @description The description of the alert. This is a free text field and can be used to provide additional information about the alert e.g. the reasons for adding it.It is limited to 4000 characters when migrating an alert. + * @example Alert description + */ + description?: string + /** + * @description The user, staff member, approving person or organisation that authorised the alert to be added. This is a free text field and can be used to record the name of the person who authorised the alert. It is limited to 40 characters. + * @example A. Nurse, An Agency + */ + authorisedBy?: string + /** + * Format: date + * @description The date the alert should be active from. The active from date can be in the past or the future, but should be on or before the active to date + * @example 2021-09-27 + */ + activeFrom: string + /** + * Format: date + * @description The date the alert should be active until. If not provided, the alert will be active indefinitely. The active to date can be in the past or the future, but should be on or after the active from date + * @example 2022-07-15 + */ + activeTo?: string + /** + * Format: date-time + * @description The date and time the alert was created. + * @example 2022-07-15'H'23:03:01.123456 + */ + createdAt: string + /** + * @description The user id of the person who created the alert. + * @example AB11DZ + */ + createdBy: string + /** + * @description The display name of the person who created the alert. + * @example A Jones + */ + createdByDisplayName: string + /** + * Format: date-time + * @description The date and time the alert was last modified. Only provided if the alert has been modified since creation. + * @example 2022-07-15'H'23:03:01.123456 + */ + lastModifiedAt?: string + /** + * @description The user id of the person who last modified the alert. Required if lastModifiedAt has been supplied. + * @example AB11DZ + */ + lastModifiedBy?: string + /** + * @description The displayable name of the person who last modified the alert. Required if lastModifiedAt has been supplied. + * @example U Dated + */ + lastModifiedByDisplayName?: string + isActive: boolean + } + ResyncedAlert: { + /** + * Format: int64 + * @description The internal NOMIS id for the offender booking. An alert in NOMIS is uniquely identified by the offender booking id and alert sequence.This is returned as part of the resync alert response for mapping between NOMIS and DPS. + * @example 12345 + */ + offenderBookId: number + /** + * Format: int32 + * @description The NOMIS alert sequence. An alert in NOMIS is uniquely identified by the offender booking id and alert sequence.This is returned as part of the resync alert response for mapping between NOMIS and DPS. + * @example 2 + */ + alertSeq: number + /** + * Format: uuid + * @description The unique identifier assigned to the alert + * @example 8cdadcf3-b003-4116-9956-c99bd8df6a00 + */ + alertUuid: string + } + /** @description The alert data to use to create an alert in the service */ + CreateAlert: { + /** + * @description The alert code for the alert. A person can only have one alert using each code active at any one time. The alert code must exist and be active. + * @example ABC + */ + alertCode: string + /** + * @description The description of the alert. This is a free text field and can be used to provide additional information about the alert e.g. the reasons for adding it.It is limited to 4000 characters. + * @example Alert description + */ + description?: string + /** + * @description The user, staff member, approving person or organisation that authorised the alert to be added. This is a free text field and can be used to record the name of the person who authorised the alert. It is limited to 40 characters. + * @example A. Nurse, An Agency + */ + authorisedBy?: string + /** + * Format: date + * @description The date the alert should be active from. If not provided, the alert will be active from the current date. The active from date can be in the past or the future, but must be before the active to date + * @example 2021-09-27 + */ + activeFrom?: string + /** + * Format: date + * @description The date the alert should be active until. If not provided, the alert will be active indefinitely. The active to date can be in the past or the future, but must be after the active from date + * @example 2022-07-15 + */ + activeTo?: string + } + BulkPlan: { + /** Format: uuid */ + id: string + } + /** @description The request body for creating a new alert type */ + CreateAlertTypeRequest: { + /** + * @description The short code for the alert type + * @example A + */ + code: string + /** + * @description The description of the alert type + * @example Alert type description + */ + description: string + } + /** @description An alert code used to categorise alerts */ + AlertCode: { + /** + * @description The short code for the alert type + * @example A + */ + alertTypeCode: string + /** + * @description The short code for the alert code. Usually starts with the alert type code + * @example ABC + */ + code: string + /** + * @description The description of the alert code + * @example Alert code description + */ + description: string + /** + * Format: int32 + * @description The sequence number of the alert code within the alert type. Used for ordering alert codes correctly in lists and drop downs. A value of 0 indicates this is the default alert code for the alert type + * @example 3 + */ + listSequence: number + /** + * @description Indicates that the alert code is active and can be used. Inactive alert codes are not returned by default in the API + * @example true + */ + isActive: boolean + /** + * Format: date-time + * @description The date and time the alert code was created + * @example 2021-09-27T14:19:25 + */ + createdAt: string + /** + * @description The username of the user who created the alert code + * @example USER1234 + */ + createdBy: string + /** + * Format: date-time + * @description The date and time the alert code was last modified + * @example 2022-07-15T15:24:56 + */ + modifiedAt?: string + /** + * @description The username of the user who last modified the alert code + * @example USER1234 + */ + modifiedBy?: string + /** + * Format: date-time + * @description The date and time the alert code was deactivated + * @example 2023-11-08T09:53:34 + */ + deactivatedAt?: string + /** + * @description The username of the user who deactivated the alert code + * @example USER1234 + */ + deactivatedBy?: string + } + /** @description An alert type used to categorise alerts */ + AlertType: { + /** + * @description The short code for the alert type + * @example A + */ + code: string + /** + * @description The description of the alert type + * @example Alert type description + */ + description: string + /** + * Format: int32 + * @description The sequence number of the alert type. Used for ordering alert types correctly in lists and drop downs. A value of 0 indicates this is the default alert type + * @example 3 + */ + listSequence: number + /** + * @description Indicates that the alert type is active and can be used. Inactive alert types are not returned by default in the API + * @example true + */ + isActive: boolean + /** + * Format: date-time + * @description The date and time the alert type was created + * @example 2021-09-27T14:19:25 + */ + createdAt: string + /** + * @description The username of the user who created the alert type + * @example USER1234 + */ + createdBy: string + /** + * Format: date-time + * @description The date and time the alert type was last modified + * @example 2022-07-15T15:24:56 + */ + modifiedAt?: string + /** + * @description The username of the user who last modified the alert type + * @example USER1234 + */ + modifiedBy?: string + /** + * Format: date-time + * @description The date and time the alert type was deactivated + * @example 2023-11-08T09:53:34 + */ + deactivatedAt?: string + /** + * @description The username of the user who deactivated the alert type + * @example USER1234 + */ + deactivatedBy?: string + /** @description The alert codes associated with this alert type */ + alertCodes: components['schemas']['AlertCode'][] + } + /** @description The request body for creating a new alert code */ + CreateAlertCodeRequest: { + /** + * @description The short code for the alert code + * @example A + */ + code: string + /** + * @description The description of the alert code + * @example Alert code description + */ + description: string + /** + * @description The short code for the parent type + * @example A + */ + parent: string + } + /** @description The request body for updating the properties of an alert type */ + UpdateAlertTypeRequest: { + /** + * @description The new property value(s) to be updated onto an alert type + * @example New description value for an alert type + */ + description: string + } + /** @description The request body for updating the properties of an alert code */ + UpdateAlertCodeRequest: { + /** + * @description The new property value(s) to be updated onto an alert code + * @example New description value for an alert code + */ + description: string + } + PageAlert: { + /** Format: int64 */ + totalElements?: number + /** Format: int32 */ + totalPages?: number + first?: boolean + last?: boolean + /** Format: int32 */ + size?: number + content?: components['schemas']['Alert'][] + /** Format: int32 */ + number?: number + sort?: components['schemas']['Sortnull'] + /** Format: int32 */ + numberOfElements?: number + pageable?: components['schemas']['Pageablenull'] + empty?: boolean + } + Pageablenull: { + /** Format: int64 */ + offset?: number + sort?: components['schemas']['Sortnull'] + /** Format: int32 */ + pageSize?: number + paged?: boolean + /** Format: int32 */ + pageNumber?: number + unpaged?: boolean + } + Sortnull: { + empty?: boolean + sorted?: boolean + unsorted?: boolean + } + BulkPlanCounts: { + /** Format: int32 */ + existingAlerts: number + /** Format: int32 */ + created: number + /** Format: int32 */ + updated: number + /** Format: int32 */ + expired: number + } + BulkPlanStatus: { + /** Format: date-time */ + createdAt?: string + createdBy?: string + createdByDisplayName?: string + /** Format: date-time */ + startedAt?: string + startedBy?: string + startedByDisplayName?: string + /** Format: date-time */ + completedAt?: string + counts?: components['schemas']['BulkPlanCounts'] + } + BulkPlanPrisoners: { + prisoners: components['schemas']['PrisonerSummary'][] + } + PrisonerSummary: { + prisonNumber: string + firstName: string + lastName: string + prisonCode?: string + cellLocation?: string + } + BulkPlanAffect: { + counts: components['schemas']['BulkPlanCounts'] + } + } + responses: never + parameters: never + requestBodies: never + headers: never + pathItems: never +} +export type $defs = Record +export interface operations { + retrieveAlert: { + parameters: { + query?: never + header?: never + path: { + /** @description Alert unique identifier */ + alertUuid: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert found */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Alert'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description The alert associated with this identifier was not found. */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + updateAlert: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + /** @description The source of the request. Will default to 'DPS' if not suppliedThis value will be assigned to the additionalInformation.source property in published domain events. A source value of 'NOMIS' will allow any username value that is less than 32 characters to be supplied. If this username is not found, its value will be used for the user display name property. A source value of 'NOMIS' will also allow no username value to be supplied and will use 'NOMIS' for both the username and display name properties. */ + Source?: string + } + path: { + /** @description Alert unique identifier */ + alertUuid: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['UpdateAlert'] + } + } + responses: { + /** @description Alert updated successfully */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Alert'] + } + } + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + deleteAlert: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + /** @description The source of the request. Will default to 'DPS' if not suppliedThis value will be assigned to the additionalInformation.source property in published domain events. A source value of 'NOMIS' will allow any username value that is less than 32 characters to be supplied. If this username is not found, its value will be used for the user display name property. A source value of 'NOMIS' will also allow no username value to be supplied and will use 'NOMIS' for both the username and display name properties. */ + Source?: string + } + path: { + /** @description Alert unique identifier */ + alertUuid: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert deleted */ + 204: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Alert was not found or already deleted */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + retrievePrisonerAlerts: { + parameters: { + query?: { + includeInactive?: boolean + } + header?: never + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': string[] + } + } + responses: { + /** @description Alerts found */ + 200: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['AlertsResponse'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + '*/*': components['schemas']['ErrorResponse'] + } + } + } + } + resyncPrisonerAlerts: { + parameters: { + query?: never + header?: never + path: { + /** + * @description Unique identifier of the prisoner. Aliases: offender number, prisoner number, offender id or NOMS id + * @example A1234AA + */ + prisonNumber: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['ResyncAlert'][] + } + } + responses: { + /** @description Resync of alerts successful */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ResyncedAlert'][] + } + } + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + retrievePrisonerAlerts_1: { + parameters: { + query?: { + /** + * @description Return only active (true) or inactive (false) alerts. If not provided or a null value is supplied, all alerts are returned + * @example true + */ + isActive?: boolean + /** + * @description Filter by alert type code or codes. Supply a comma separated list of alert type codes to filter by more than one code + * @example M + */ + alertType?: string + /** + * @description Filter by alert code or codes. Supply a comma separated list of alert codes to filter by more than one code + * @example AS + */ + alertCode?: string + /** + * @description Filter alerts that have an active on date or after the supplied date + * @example 2023-09-27 + */ + activeFromStart?: string + /** + * @description Filter alerts that have an active on date up to or before the supplied date + * @example 2021-11-15 + */ + activeFromEnd?: string + /** + * @description Filter alerts that contain the search text in their description or authorised by. The search is case insensitive and will match any part of the description or authorised by text + * @example Search text + */ + search?: string + /** @description Zero-based page index (0..N) */ + page?: unknown + /** @description The size of the page to be returned */ + size?: unknown + /** @description Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. */ + sort?: unknown[] + } + header?: never + path: { + /** + * @description Prison number of the prisoner. Also referred to as the offender number, offender id or NOMS id + * @example A1234AA + */ + prisonNumber: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alerts found */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['PageAlert'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + createPrisonerAlert: { + parameters: { + query?: { + /** @description Allows the creation of an alert using an inactive code. Intended only for use by the Alerts UI when the user has the ‘Manage Alerts in Bulk for Prison Estate’ role. Defaults to false */ + allowInactiveCode?: boolean + } + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + /** @description The source of the request. Will default to 'DPS' if not suppliedThis value will be assigned to the additionalInformation.source property in published domain events. A source value of 'NOMIS' will allow any username value that is less than 32 characters to be supplied. If this username is not found, its value will be used for the user display name property. A source value of 'NOMIS' will also allow no username value to be supplied and will use 'NOMIS' for both the username and display name properties. */ + Source?: string + } + path: { + /** + * @description Prison number of the prisoner. Also referred to as the offender number, offender id or NOMS id + * @example A1234AA + */ + prisonNumber: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['CreateAlert'] + } + } + responses: { + /** @description Alert created successfully */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['Alert'] + } + } + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Conflict, the person already has an active alert using the supplied alert code */ + 409: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + createPlan: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + /** @description The source of the request. Will default to 'DPS' if not suppliedThis value will be assigned to the additionalInformation.source property in published domain events. A source value of 'NOMIS' will allow any username value that is less than 32 characters to be supplied. If this username is not found, its value will be used for the user display name property. A source value of 'NOMIS' will also allow no username value to be supplied and will use 'NOMIS' for both the username and display name properties. */ + Source?: string + } + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description Alerts creation plan generated successfully */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['BulkPlan'] + } + } + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + startPlan: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + /** @description The source of the request. Will default to 'DPS' if not suppliedThis value will be assigned to the additionalInformation.source property in published domain events. A source value of 'NOMIS' will allow any username value that is less than 32 characters to be supplied. If this username is not found, its value will be used for the user display name property. A source value of 'NOMIS' will also allow no username value to be supplied and will use 'NOMIS' for both the username and display name properties. */ + Source?: string + } + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Start plan accepted - will run asynchronously */ + 202: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + retrieveAlertTypes: { + parameters: { + query?: { + /** @description Include inactive alert types and codes. Defaults to false */ + includeInactive?: boolean + } + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert types and codes found */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertType'][] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + createAlertType: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['CreateAlertTypeRequest'] + } + } + responses: { + /** @description Alert type created */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertType'][] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Conflict, the alert type code already exists */ + 409: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + retrieveAlertCodes: { + parameters: { + query?: { + /** @description Include inactive alert types and codes. Defaults to false */ + includeInactive?: boolean + } + header?: never + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert code retrieved */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertCode'][] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + createAlertCode: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path?: never + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['CreateAlertCodeRequest'] + } + } + responses: { + /** @description Alert code created */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertCode'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the parent alert type has not been found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Conflict, the alert code already exists */ + 409: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + updatePlan: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + /** @description The source of the request. Will default to 'DPS' if not suppliedThis value will be assigned to the additionalInformation.source property in published domain events. A source value of 'NOMIS' will allow any username value that is less than 32 characters to be supplied. If this username is not found, its value will be used for the user display name property. A source value of 'NOMIS' will also allow no username value to be supplied and will use 'NOMIS' for both the username and display name properties. */ + Source?: string + } + path: { + id: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': unknown + } + } + responses: { + /** @description Alerts creation plan generated successfully */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['BulkPlan'] + } + } + /** @description Bad request */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + updateAlertType: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path: { + alertType: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['UpdateAlertTypeRequest'] + } + } + responses: { + /** @description Alert type updated */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertType'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert type was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + reactivateAlertType: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path: { + alertType: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert type reactivated */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertType'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert type was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + deactivateAlertType: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path: { + alertType: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert type deactivated */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertType'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert type was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + retrieveAlertCode: { + parameters: { + query?: never + header?: never + path: { + alertCode: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert code retrieved */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertCode'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert code was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + updateAlertCode: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path: { + alertCode: string + } + cookie?: never + } + requestBody: { + content: { + 'application/json': components['schemas']['UpdateAlertCodeRequest'] + } + } + responses: { + /** @description Alert code updated */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertCode'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert code was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + reactivateAlertCode: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path: { + alertCode: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert code reactivated */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertCode'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert code was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + deactivateAlertCode: { + parameters: { + query?: never + header?: { + /** @description The username of the user interacting with the client service. This can be used instead of the `user_name` or `username` token claim when the client service is acting on behalf of a user. The value passed in the username header will only be used if a `user_name` or `username` token claim is not present. */ + Username?: string + } + path: { + alertCode: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert code deactivated */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertCode'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert code was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getPlanStatus: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully retrieved the status of a plan */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['BulkPlanStatus'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description No plan found with the provided identifier */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getPlanPrisoners: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully retrieved prisoners associated with a plan */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['BulkPlanPrisoners'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description No plan found with the provided identifier */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + getPlanAffect: { + parameters: { + query?: never + header?: never + path: { + id: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Successfully retrieved counts of affect of plan */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['BulkPlanAffect'] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description No plan found with the provided identifier */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } + retrieveAlertType: { + parameters: { + query?: never + header?: never + path: { + alertTypeCode: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Alert type found */ + 200: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['AlertType'][] + } + } + /** @description Unauthorised, requires a valid Oauth2 token */ + 401: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Forbidden, requires an appropriate role */ + 403: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Not found, the alert type was is not found */ + 404: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } +} diff --git a/server/@types/alertsAPI/types.ts b/server/@types/alertsAPI/types.ts new file mode 100644 index 000000000..b3c7542d6 --- /dev/null +++ b/server/@types/alertsAPI/types.ts @@ -0,0 +1,3 @@ +import { components } from '.' + +export type Alert = components['schemas']['Alert'] diff --git a/server/config.ts b/server/config.ts index cd1213af4..a13e121a3 100755 --- a/server/config.ts +++ b/server/config.ts @@ -169,6 +169,14 @@ export default { }, agent: new AgentConfig(Number(get('NON_ASSOCIATIONS_API_TIMEOUT_RESPONSE', 30000))), }, + alertsApi: { + url: get('ALERTS_API_URL', 'http://localhost:8097', requiredInProduction), + timeout: { + response: Number(get('ALERTS_API_TIMEOUT_RESPONSE', 30000)), + deadline: Number(get('ALERTS_API_TIMEOUT_DEADLINE', 30000)), + }, + agent: new AgentConfig(Number(get('ALERTS_API_TIMEOUT_RESPONSE', 30000))), + }, locationsInsidePrisonApi: { url: get('LOCATIONS_INSIDE_PRISON_API_URL', 'http://localhost:8082', requiredInProduction), timeout: { diff --git a/server/data/alertsApiClient.test.ts b/server/data/alertsApiClient.test.ts new file mode 100644 index 000000000..083c737dc --- /dev/null +++ b/server/data/alertsApiClient.test.ts @@ -0,0 +1,42 @@ +import nock from 'nock' +import config from '../config' +import TokenStore from './tokenStore' +import AlertsApiClient from './alertsApiClient' +import { ServiceUser } from '../@types/express' + +const user = {} as ServiceUser + +jest.mock('./tokenStore') + +describe('alertsApiClient', () => { + let fakeAlertsApi: nock.Scope + let alertsApiClient: AlertsApiClient + + beforeEach(() => { + fakeAlertsApi = nock(config.apis.alertsApi.url) + alertsApiClient = new AlertsApiClient() + + jest.spyOn(TokenStore.prototype, 'getToken').mockResolvedValue('accessToken') + }) + + afterEach(() => { + jest.resetAllMocks() + nock.cleanAll() + }) + + describe('getAlertsForPrisoners', () => { + it('returns data from api', async () => { + const response = { data: 'some data' } + + fakeAlertsApi + .post('/search/alerts/prison-numbers') + .matchHeader('authorization', `Bearer accessToken`) + .reply(200, response) + + const output = await alertsApiClient.getAlertsForPrisoners(['G4793VF', 'G0113GG'], user) + + expect(output).toEqual(response) + expect(nock.isDone()).toBe(true) + }) + }) +}) diff --git a/server/data/alertsApiClient.ts b/server/data/alertsApiClient.ts new file mode 100644 index 000000000..7e6e91a71 --- /dev/null +++ b/server/data/alertsApiClient.ts @@ -0,0 +1,20 @@ +import AbstractHmppsRestClient from './abstractHmppsRestClient' +import config, { ApiConfig } from '../config' +import { ServiceUser } from '../@types/express' +import { Alert } from '../@types/alertsAPI/types' + +export default class AlertsApiClient extends AbstractHmppsRestClient { + constructor() { + super('Alerts API', config.apis.alertsApi as ApiConfig) + } + + async getAlertsForPrisoners(prisonerNumbers: string[], user: ServiceUser): Promise { + return this.post( + { + path: `/search/alerts/prison-numbers`, + data: prisonerNumbers, + }, + user, + ) + } +} diff --git a/server/data/index.test.ts b/server/data/index.test.ts index 62193d0be..675b67ac2 100644 --- a/server/data/index.test.ts +++ b/server/data/index.test.ts @@ -9,13 +9,14 @@ import FrontendComponentApiClient from './frontendComponentApiClient' import CaseNotesApiClient from './caseNotesApiClient' import BookAVideoLinkApiClient from './bookAVideoLinkApiClient' import NonAssociationsApiClient from './nonAssociationsApiClient' +import AlertsApiClient from './alertsApiClient' import LocationsInsidePrisonApiClient from './locationsInsidePrisonApiClient' import NomisMappingClient from './nomisMappingClient' describe('DataAccess', () => { test('The correct rest clients are instantiated', () => { const clients = dataAccess() - expect(Object.values(clients).length).toBe(13) + expect(Object.values(clients).length).toBe(14) expect(clients.manageUsersApiClient).toBeInstanceOf(ManageUsersApiClient) expect(clients.caseNotesApiClient).toBeInstanceOf(CaseNotesApiClient) expect(clients.prisonApiClient).toBeInstanceOf(PrisonApiClient) @@ -26,6 +27,7 @@ describe('DataAccess', () => { expect(clients.bookAVideoLinkApiClient).toBeInstanceOf(BookAVideoLinkApiClient) expect(clients.frontendComponentApiClient).toBeInstanceOf(FrontendComponentApiClient) expect(clients.nonAssociationsApiClient).toBeInstanceOf(NonAssociationsApiClient) + expect(clients.alertsApiClient).toBeInstanceOf(AlertsApiClient) expect(clients.locationsInsidePrisonApiClient).toBeInstanceOf(LocationsInsidePrisonApiClient) expect(clients.nomisMappingClient).toBeInstanceOf(NomisMappingClient) }) diff --git a/server/data/index.ts b/server/data/index.ts index 2d3c1a0c7..f825e58a2 100644 --- a/server/data/index.ts +++ b/server/data/index.ts @@ -18,6 +18,7 @@ import FrontendComponentApiClient from './frontendComponentApiClient' import CaseNotesApiClient from './caseNotesApiClient' import BookAVideoLinkApiClient from './bookAVideoLinkApiClient' import NonAssociationsApiClient from './nonAssociationsApiClient' +import AlertsApiClient from './alertsApiClient' import LocationsInsidePrisonApiClient from './locationsInsidePrisonApiClient' import NomisMappingClient from './nomisMappingClient' @@ -34,6 +35,7 @@ export default function dataAccess() { bookAVideoLinkApiClient: new BookAVideoLinkApiClient(), applicationInsightsClient: appInsightsClient, nonAssociationsApiClient: new NonAssociationsApiClient(), + alertsApiClient: new AlertsApiClient(), locationsInsidePrisonApiClient: new LocationsInsidePrisonApiClient(), nomisMappingClient: new NomisMappingClient(), } diff --git a/server/services/alertsService.test.ts b/server/services/alertsService.test.ts new file mode 100644 index 000000000..a76b3e1dd --- /dev/null +++ b/server/services/alertsService.test.ts @@ -0,0 +1,58 @@ +import { when } from 'jest-when' +import atLeast from '../../jest.setup' + +import { ServiceUser } from '../@types/express' +import AlertsApiClient from '../data/alertsApiClient' +import AlertsService from './alertsService' +import { Alert } from '../@types/alertsAPI/types' + +jest.mock('../data/alertsApiClient') + +describe('Alerts service', () => { + const alertsApiClient = new AlertsApiClient() + const alertsService = new AlertsService(alertsApiClient) + + const user = { + activeCaseLoadId: 'MDI', + } as ServiceUser + + describe('getAlertsForPrisoners', () => { + it('should respond with the alerts for the prisoners given, from the alerts API', async () => { + const apiResponse: Alert[] = [ + { + alertUuid: '6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa', + prisonNumber: 'G4793VF', + alertCode: { + alertTypeCode: 'X', + alertTypeDescription: 'Security', + code: 'XTACT', + description: 'Terrorism Act or Related Offence', + }, + description: 'TACT', + authorisedBy: 'TEST_GEN', + activeFrom: '2023-03-20', + activeTo: '2026-03-20', + isActive: true, + createdAt: '2023-03-20T14:14:27', + createdBy: 'SCH_ACTIVITY', + createdByDisplayName: 'Schedule Activity', + lastModifiedAt: '2023-07-20T14:14:27', + lastModifiedBy: 'TEST_GEN', + lastModifiedByDisplayName: 'TESTER', + activeToLastSetAt: '2023-07-20T14:14:27', + activeToLastSetBy: 'TEST_GEN', + activeToLastSetByDisplayName: 'TESTER', + }, + ] + + const prisonerNumbers = ['G4793VF', 'Q6755TY'] + + when(alertsApiClient.getAlertsForPrisoners).calledWith(atLeast(prisonerNumbers)).mockResolvedValue(apiResponse) + + const actualResult = await alertsService.getAlertsUsingPrisonerNumbers(prisonerNumbers, user) + + expect(actualResult).toEqual(apiResponse) + expect(alertsApiClient.getAlertsForPrisoners).toHaveBeenCalledWith(prisonerNumbers, user) + }) + }) +}) diff --git a/server/services/alertsService.ts b/server/services/alertsService.ts new file mode 100644 index 000000000..a4516e542 --- /dev/null +++ b/server/services/alertsService.ts @@ -0,0 +1,11 @@ +import { Alert } from '../@types/alertsAPI/types' +import { ServiceUser } from '../@types/express' +import AlertsApiClient from '../data/alertsApiClient' + +export default class AlertsService { + constructor(private readonly alertsApiClient: AlertsApiClient) {} + + async getAlertsUsingPrisonerNumbers(prisonerNumbers: string[], user: ServiceUser): Promise { + return this.alertsApiClient.getAlertsForPrisoners(prisonerNumbers, user) + } +} diff --git a/server/services/healthCheck.ts b/server/services/healthCheck.ts index 272ecfc0b..a0ff9da8a 100644 --- a/server/services/healthCheck.ts +++ b/server/services/healthCheck.ts @@ -63,6 +63,7 @@ const apiChecks = [ `${config.apis.nonAssociationsApi.url}/health/ping`, config.apis.nonAssociationsApi.agent, ), + service('alertsApi', `${config.apis.alertsApi.url}/health/ping`, config.apis.alertsApi.agent), service('bookAVideoLinkApi', `${config.apis.bookAVideoLinkApi.url}/health/ping`, config.apis.bookAVideoLinkApi.agent), service( 'locationsInsidePrisonApi', diff --git a/server/services/index.test.ts b/server/services/index.test.ts index 6f33f7e78..0f59a7f7f 100644 --- a/server/services/index.test.ts +++ b/server/services/index.test.ts @@ -10,6 +10,7 @@ import AlertsFilterService from './alertsFilterService' import BookAVideoLinkService from './bookAVideoLinkService' import NonAssociationsService from './nonAssociationsService' import LocationMappingService from './locationMappingService' +import AlertsService from './alertsService' jest.mock('applicationinsights') @@ -17,7 +18,7 @@ describe('Services', () => { test('The correct services are instantiated', () => { const servicesList = services() - expect(Object.values(servicesList).length).toBe(11) + expect(Object.values(servicesList).length).toBe(12) expect(servicesList.userService).toBeInstanceOf(UserService) expect(servicesList.prisonService).toBeInstanceOf(PrisonService) expect(servicesList.ukBankHolidayService).toBeInstanceOf(BankHolidayService) @@ -28,6 +29,7 @@ describe('Services', () => { expect(servicesList.metricsService).toBeInstanceOf(MetricsService) expect(servicesList.bookAVideoLinkService).toBeInstanceOf(BookAVideoLinkService) expect(servicesList.nonAssociationsService).toBeInstanceOf(NonAssociationsService) + expect(servicesList.alertsService).toBeInstanceOf(AlertsService) expect(servicesList.locationMappingService).toBeInstanceOf(LocationMappingService) }) }) diff --git a/server/services/index.ts b/server/services/index.ts index 64bd898f3..26bcd0ac7 100644 --- a/server/services/index.ts +++ b/server/services/index.ts @@ -10,6 +10,7 @@ import AlertsFilterService from './alertsFilterService' import BookAVideoLinkService from './bookAVideoLinkService' import NonAssociationsService from './nonAssociationsService' import LocationMappingService from './locationMappingService' +import AlertsService from './alertsService' export default function services() { const { @@ -23,6 +24,7 @@ export default function services() { applicationInsightsClient, caseNotesApiClient, nonAssociationsApiClient, + alertsApiClient, locationsInsidePrisonApiClient, nomisMappingClient, } = dataAccess() @@ -41,6 +43,7 @@ export default function services() { unlockListService: new UnlockListService(prisonerSearchApiClient, activitiesApiClient, alertsFilterService), metricsService: new MetricsService(applicationInsightsClient), nonAssociationsService: new NonAssociationsService(nonAssociationsApiClient, prisonService), + alertsService: new AlertsService(alertsApiClient), locationMappingService: new LocationMappingService(locationsInsidePrisonApiClient, nomisMappingClient), } } From ca712a22c1f872195062f2600cdd076593198aeb Mon Sep 17 00:00:00 2001 From: Natalie Clamp Date: Thu, 9 Jan 2025 15:12:46 +0000 Subject: [PATCH 2/5] SAA-2272: Replace alerts code --- .../e2e/appointments/createAppointment.cy.ts | 4 +- .../createAppointmentBackLinks.cy.ts | 4 +- .../createAppointmentCheckAnswers.cy.ts | 4 +- .../createInCellAppointment.cy.ts | 4 +- .../createRepeatAppointment.cy.ts | 4 +- .../createRetrospectiveAppointment.cy.ts | 4 +- .../appointments/duplicateAppointment.cy.ts | 4 +- .../e2e/appointments/editAppointments.cy.ts | 8 +- .../reviewNonAssociationsPage.cy.ts | 4 +- .../activitiesApi/getOffenderAlerts.json | 72 ---- .../getOffenderAlertsA8644DY.json | 16 - .../getOffenderAlertsG0995GW.json | 16 - .../getOffenderAlertsG0995GWG6123VU.json | 16 - .../fixtures/alertsApi/getPrisonerAlerts.json | 124 ++++++ .../alertsApi/getPrisonerAlertsA8644DY.json | 28 ++ .../alertsApi/getPrisonerAlertsG0995GW.json | 28 ++ .../getPrisonerAlertsG0995GWG6123VU.json | 28 ++ server/data/alertsApiClient.ts | 2 +- server/data/prisonApiClient.ts | 11 +- .../create-and-edit/createRoutes.ts | 7 +- .../create-and-edit/editRoutes.ts | 7 +- .../handlers/reviewPrisoners.test.ts | 28 +- .../handlers/reviewPrisoners.ts | 13 +- .../handlers/reviewPrisonersAlerts.test.ts | 26 +- .../handlers/reviewPrisonersAlerts.ts | 12 +- server/services/alertsService.test.ts | 399 +++++++++++++++++- server/services/alertsService.ts | 113 ++++- server/services/index.ts | 3 +- server/services/prisonService.ts | 6 +- server/services/prisonerAlertsService.test.ts | 340 --------------- server/services/prisonerAlertsService.ts | 129 ------ .../review-prisoners-alerts.njk | 4 +- 32 files changed, 783 insertions(+), 685 deletions(-) delete mode 100644 integration_tests/fixtures/activitiesApi/getOffenderAlerts.json delete mode 100644 integration_tests/fixtures/activitiesApi/getOffenderAlertsA8644DY.json delete mode 100644 integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GW.json delete mode 100644 integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GWG6123VU.json create mode 100644 integration_tests/fixtures/alertsApi/getPrisonerAlerts.json create mode 100644 integration_tests/fixtures/alertsApi/getPrisonerAlertsA8644DY.json create mode 100644 integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GW.json create mode 100644 integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GWG6123VU.json delete mode 100644 server/services/prisonerAlertsService.test.ts delete mode 100644 server/services/prisonerAlertsService.ts diff --git a/integration_tests/e2e/appointments/createAppointment.cy.ts b/integration_tests/e2e/appointments/createAppointment.cy.ts index 4988fccfa..ecaf617d8 100644 --- a/integration_tests/e2e/appointments/createAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createAppointment.cy.ts @@ -14,7 +14,7 @@ import getScheduledEvents from '../../fixtures/activitiesApi/getScheduledEventsM import getAppointmentSeries from '../../fixtures/activitiesApi/getAppointmentSeries.json' import getGroupAppointmentSeriesDetails from '../../fixtures/activitiesApi/getGroupAppointmentSeriesDetails.json' import getGroupAppointmentDetails from '../../fixtures/activitiesApi/getGroupAppointmentDetails.json' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlerts.json' +import getPrisonerAlerts from '../../fixtures/alertsApi/getPrisonerAlerts.json' import getNonAssociationsBetweenA8644DYA1350DZ from '../../fixtures/nonAssociationsApi/getNonAssociationsBetweenA8644DYA1350DZ.json' import HowToAddPrisonersPage from '../../pages/appointments/create-and-edit/howToAddPrisonersPage' import ReviewPrisonersPage from '../../pages/appointments/create-and-edit/reviewPrisonersPage' @@ -80,7 +80,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', getNonAssociationsBetweenA8644DYA1350DZ) }) diff --git a/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts b/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts index 628d8331e..55473fa1c 100644 --- a/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts +++ b/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts @@ -25,7 +25,7 @@ import TierPage from '../../pages/appointments/create-and-edit/tierPage' import HostPage from '../../pages/appointments/create-and-edit/hostPage' import HowToAddPrisonersPage from '../../pages/appointments/create-and-edit/howToAddPrisonersPage' import ReviewPrisonersPage from '../../pages/appointments/create-and-edit/reviewPrisonersPage' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlertsA8644DY.json' +import getPrisonerAlertsA8644DY from '../../fixtures/alertsApi/getPrisonerAlertsA8644DY.json' import ReviewPrisonerAlertsPage from '../../pages/appointments/create-and-edit/reviewPrisonerAlertsPage' context('Create group appointment - back links', () => { @@ -52,7 +52,7 @@ context('Create group appointment - back links', () => { cy.stubEndpoint('POST', '/appointment-series', getAppointmentSeries) cy.stubEndpoint('GET', '/appointment-series/10/details', getAppointmentSeriesDetails) cy.stubEndpoint('GET', '/appointments/11/details', getAppointmentDetails) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsA8644DY) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts b/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts index 912a7d451..e0a0ea233 100644 --- a/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts +++ b/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts @@ -27,7 +27,7 @@ import TierPage from '../../pages/appointments/create-and-edit/tierPage' import HostPage from '../../pages/appointments/create-and-edit/hostPage' import HowToAddPrisonersPage from '../../pages/appointments/create-and-edit/howToAddPrisonersPage' import ReviewPrisonersPage from '../../pages/appointments/create-and-edit/reviewPrisonersPage' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlertsA8644DY.json' +import getPrisonerAlertsA8644DY from '../../fixtures/alertsApi/getPrisonerAlertsA8644DY.json' import ReviewPrisonerAlertsPage from '../../pages/appointments/create-and-edit/reviewPrisonerAlertsPage' context('Create group appointment - check answers change links', () => { @@ -66,7 +66,7 @@ context('Create group appointment - check answers change links', () => { cy.stubEndpoint('POST', '/appointment-series', getAppointmentSeries) cy.stubEndpoint('GET', '/appointment-series/10/details', getAppointmentSeriesDetails) cy.stubEndpoint('GET', '/appointments/11/details', getAppointmentDetails) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts?', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsA8644DY) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createInCellAppointment.cy.ts b/integration_tests/e2e/appointments/createInCellAppointment.cy.ts index 699d0ea27..e1f0ac8fc 100644 --- a/integration_tests/e2e/appointments/createInCellAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createInCellAppointment.cy.ts @@ -26,7 +26,7 @@ import getPrisonerA8644DY from '../../fixtures/prisonerSearchApi/getPrisoner-MDI import getAppointmentSeriesDetails from '../../fixtures/activitiesApi/getAppointmentSeriesDetails.json' import getAppointmentDetails from '../../fixtures/activitiesApi/getAppointmentDetails.json' import ReviewPrisonerAlertsPage from '../../pages/appointments/create-and-edit/reviewPrisonerAlertsPage' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlertsA8644DY.json' +import getPrisonerAlerts from '../../fixtures/alertsApi/getPrisonerAlertsA8644DY.json' context('Create group appointment', () => { const tomorrow = addDays(new Date(), 1) @@ -58,7 +58,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts b/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts index 824b0a8fb..83ef65698 100644 --- a/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts @@ -29,7 +29,7 @@ import ExtraInformationPage from '../../pages/appointments/create-and-edit/extra import SchedulePage from '../../pages/appointments/create-and-edit/schedulePage' import TierPage from '../../pages/appointments/create-and-edit/tierPage' import HostPage from '../../pages/appointments/create-and-edit/hostPage' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlerts.json' +import getPrisonerAlerts from '../../fixtures/alertsApi/getPrisonerAlerts.json' import ReviewPrisonerAlertsPage, { arsonistBadge, catABadge, @@ -86,7 +86,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts?', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts b/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts index bfcfaf8e5..1e036033f 100644 --- a/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts @@ -14,7 +14,7 @@ import getScheduledEvents from '../../fixtures/activitiesApi/getScheduledEventsM import getAppointmentSeries from '../../fixtures/activitiesApi/getAppointmentSeries.json' import getGroupAppointmentSeriesDetails from '../../fixtures/activitiesApi/getGroupAppointmentSeriesDetails.json' import getGroupAppointmentDetails from '../../fixtures/activitiesApi/getGroupAppointmentDetails.json' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlerts.json' +import getPrisonerAlerts from '../../fixtures/alertsApi/getPrisonerAlerts.json' import HowToAddPrisonersPage from '../../pages/appointments/create-and-edit/howToAddPrisonersPage' import ReviewPrisonersPage from '../../pages/appointments/create-and-edit/reviewPrisonersPage' import ReviewPrisonerAlertsPage, { @@ -73,7 +73,7 @@ context('Create a retrospective appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/duplicateAppointment.cy.ts b/integration_tests/e2e/appointments/duplicateAppointment.cy.ts index aa36d026a..2686be523 100644 --- a/integration_tests/e2e/appointments/duplicateAppointment.cy.ts +++ b/integration_tests/e2e/appointments/duplicateAppointment.cy.ts @@ -15,7 +15,7 @@ import ReviewPrisonersPage from '../../pages/appointments/create-and-edit/review import DateAndTimePage from '../../pages/appointments/create-and-edit/dateAndTimePage' import SchedulePage from '../../pages/appointments/create-and-edit/schedulePage' import getGroupAppointmentSeriesDetails from '../../fixtures/activitiesApi/getGroupAppointmentSeriesDetails.json' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlerts.json' +import getPrisonerAlerts from '../../fixtures/alertsApi/getPrisonerAlerts.json' import getScheduledEvents from '../../fixtures/activitiesApi/getScheduledEventsMdi20230202.json' import { formatDate } from '../../../server/utils/utils' import CheckAnswersPage from '../../pages/appointments/create-and-edit/checkAnswersPage' @@ -56,7 +56,7 @@ context('Duplicate appointment', () => { JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) cy.stubEndpoint('GET', '/appointment-series/10/details', getGroupAppointmentSeriesDetails) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) cy.stubEndpoint('POST', `/scheduled-events/prison/MDI\\?date=${tomorrowFormatted}`, getScheduledEvents) cy.stubEndpoint('POST', '/appointment-series', getAppointmentSeries) }) diff --git a/integration_tests/e2e/appointments/editAppointments.cy.ts b/integration_tests/e2e/appointments/editAppointments.cy.ts index cd5aabc22..50c6e8c7a 100644 --- a/integration_tests/e2e/appointments/editAppointments.cy.ts +++ b/integration_tests/e2e/appointments/editAppointments.cy.ts @@ -7,8 +7,8 @@ import getPrisonPrisonersG0995GW from '../../fixtures/prisonerSearchApi/getPriso import getPrisonPrisonersG6123VU from '../../fixtures/prisonerSearchApi/getPrisonPrisoners-MDI-G6123VU.json' import getPrisonerG0995GW from '../../fixtures/prisonerSearchApi/getPrisoner-MDI-G0995GW.json' import getPrisonerG6123VU from '../../fixtures/prisonerSearchApi/getPrisoner-MDI-G6123VU.json' -import getOffenderAlertsG0995GW from '../../fixtures/activitiesApi/getOffenderAlertsG0995GW.json' -import getOffenderAlertsG0995GWG6123VU from '../../fixtures/activitiesApi/getOffenderAlertsG0995GWG6123VU.json' +import getPrisonerAlertsG0995GW from '../../fixtures/alertsApi/getPrisonerAlertsG0995GW.json' +import getPrisonerAlertsG0995GWG6123VU from '../../fixtures/alertsApi/getPrisonerAlertsG0995GWG6123VU.json' import getNonAssociationsBetweenG0995GWA1350DZ from '../../fixtures/nonAssociationsApi/getNonAssociationsBetweenG0995GWA1350DZ.json' import getNonAssociationsBetweenG0995GWG6123VU from '../../fixtures/nonAssociationsApi/getNonAssociationsBetweenG0995GWG6123VU.json' import getPrisonPrisonersG0995GWA1351DZ from '../../fixtures/prisonerSearchApi/postPrisonerNumbers-A1350DZ-G0995GW.json' @@ -61,8 +61,8 @@ context('Edit appointment', () => { cy.stubEndpoint('GET', '/prison/MDI/prisoners\\?term=saunders&size=50', getPrisonPrisonersG6123VU) cy.stubEndpoint('GET', '/prisoner/G0995GW', getPrisonerG0995GW) cy.stubEndpoint('GET', '/prisoner/G6123VU', getPrisonerG6123VU) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlertsG0995GW) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlertsG0995GWG6123VU) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsG0995GW) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsG0995GWG6123VU) cy.stubEndpoint('POST', '/non-associations/between', getNonAssociationsBetweenG0995GWA1350DZ) cy.stubEndpoint('POST', '/prisoner-search/prisoner-numbers', getPrisonPrisonersG0995GWA1351DZ) diff --git a/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts b/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts index de8445fcd..113e92db1 100644 --- a/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts +++ b/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts @@ -14,7 +14,7 @@ import getScheduledEvents from '../../fixtures/activitiesApi/getScheduledEventsM import getAppointmentSeries from '../../fixtures/activitiesApi/getAppointmentSeries.json' import getGroupAppointmentSeriesDetails from '../../fixtures/activitiesApi/getGroupAppointmentSeriesDetails.json' import getGroupAppointmentDetails from '../../fixtures/activitiesApi/getGroupAppointmentDetails.json' -import getOffenderAlerts from '../../fixtures/activitiesApi/getOffenderAlerts.json' +import getPrisonerAlerts from '../../fixtures/alertsApi/getPrisonerAlerts.json' import getNonAssociationsBetweenA8644DYA1350DZ from '../../fixtures/nonAssociationsApi/getNonAssociationsBetweenA8644DYA1350DZ.json' import HowToAddPrisonersPage from '../../pages/appointments/create-and-edit/howToAddPrisonersPage' import ReviewPrisonersPage from '../../pages/appointments/create-and-edit/reviewPrisonersPage' @@ -73,7 +73,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/api/bookings/offenderNo/MDI/alerts', getOffenderAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', getNonAssociationsBetweenA8644DYA1350DZ) }) diff --git a/integration_tests/fixtures/activitiesApi/getOffenderAlerts.json b/integration_tests/fixtures/activitiesApi/getOffenderAlerts.json deleted file mode 100644 index 59a5fb1d8..000000000 --- a/integration_tests/fixtures/activitiesApi/getOffenderAlerts.json +++ /dev/null @@ -1,72 +0,0 @@ -[ - { - "active": false, - "alertId": 1, - "bookingId": 830082, - "offenderNo": "A1351DZ", - "alertType": "X", - "alertTypeDescription": "Security", - "alertCode": "XNR", - "alertCodeDescription": "Not For Release", - "dateCreated": "2010-08-19", - "dateExpires": "2012-03-02", - "modifiedDateTime": "2017-05-09T21:50:35.311477", - "expired": true - }, - { - "active": true, - "alertId": 2, - "bookingId": 830082, - "offenderNo": "A1351DZ", - "alertType": "X", - "alertTypeDescription": "Security", - "alertCode": "XTACT", - "alertCodeDescription": "Terrorism Act or Related Offence", - "dateCreated": "2023-03-20", - "dateExpires": "2012-03-02", - "modifiedDateTime": "2017-05-09T21:57:04.212529", - "expired": false - }, - { - "active": true, - "alertId": 3, - "bookingId": 830082, - "offenderNo": "A1351DZ", - "alertType": "X", - "alertTypeDescription": "Security", - "alertCode": "XA", - "alertCodeDescription": "Arsonist", - "dateCreated": "2023-03-20", - "dateExpires": "2012-03-02", - "modifiedDateTime": "2023-03-20T21:57:04.212529", - "expired": false - }, - { - "active": true, - "alertId": 4, - "bookingId": 830082, - "offenderNo": "A1351DZ", - "alertType": "R", - "alertTypeDescription": "Risk", - "alertCode": "RNO121", - "alertCodeDescription": "No 1 to 1 with this prisoner", - "dateCreated": "2023-03-20", - "dateExpires": "2027-03-20", - "modifiedDateTime": "2023-03-20T21:57:04.212529", - "expired": false - }, - { - "active": true, - "alertId": 5, - "bookingId": 830082, - "offenderNo": "A1351DZ", - "alertType": "R", - "alertTypeDescription": "Risk", - "alertCode": "XCO", - "alertCodeDescription": "Corruptor", - "dateCreated": "2023-03-20", - "dateExpires": "2027-03-20", - "modifiedDateTime": "2023-03-20T21:57:04.212529", - "expired": false - } -] diff --git a/integration_tests/fixtures/activitiesApi/getOffenderAlertsA8644DY.json b/integration_tests/fixtures/activitiesApi/getOffenderAlertsA8644DY.json deleted file mode 100644 index cdd710aee..000000000 --- a/integration_tests/fixtures/activitiesApi/getOffenderAlertsA8644DY.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "active": true, - "alertId": 1, - "bookingId": 830082, - "offenderNo": "A8644DY", - "alertType": "X", - "alertTypeDescription": "Security", - "alertCode": "XNR", - "alertCodeDescription": "Not For Release", - "dateCreated": "2010-08-19", - "dateExpires": "2012-03-02", - "modifiedDateTime": "2017-05-09T21:50:35.311477", - "expired": false - } -] diff --git a/integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GW.json b/integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GW.json deleted file mode 100644 index 7b266074d..000000000 --- a/integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GW.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "active": true, - "alertId": 1, - "bookingId": 830082, - "offenderNo": "G0995GW", - "alertType": "X", - "alertTypeDescription": "Security", - "alertCode": "XNR", - "alertCodeDescription": "Not For Release", - "dateCreated": "2010-08-19", - "dateExpires": "2012-03-02", - "modifiedDateTime": "2017-05-09T21:50:35.311477", - "expired": false - } -] diff --git a/integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GWG6123VU.json b/integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GWG6123VU.json deleted file mode 100644 index 7b266074d..000000000 --- a/integration_tests/fixtures/activitiesApi/getOffenderAlertsG0995GWG6123VU.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "active": true, - "alertId": 1, - "bookingId": 830082, - "offenderNo": "G0995GW", - "alertType": "X", - "alertTypeDescription": "Security", - "alertCode": "XNR", - "alertCodeDescription": "Not For Release", - "dateCreated": "2010-08-19", - "dateExpires": "2012-03-02", - "modifiedDateTime": "2017-05-09T21:50:35.311477", - "expired": false - } -] diff --git a/integration_tests/fixtures/alertsApi/getPrisonerAlerts.json b/integration_tests/fixtures/alertsApi/getPrisonerAlerts.json new file mode 100644 index 000000000..70b8fd62b --- /dev/null +++ b/integration_tests/fixtures/alertsApi/getPrisonerAlerts.json @@ -0,0 +1,124 @@ +{ + "content": [ + { + "alertUuid": "6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa", + "prisonNumber": "A1351DZ", + "alertCode": { + "alertTypeCode": "X", + "alertTypeDescription": "Security", + "code": "XNR", + "description": "Not For Release" + }, + "description": "Not For Release", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": false, + "createdAt": "2023-03-20T14:14:27", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + }, + { + "alertUuid": "527b44f6-182e-4f5b-94c7-8f974bc77c64", + "prisonNumber": "A1351DZ", + "alertCode": { + "alertTypeCode": "X", + "alertTypeDescription": "Security", + "code": "XTACT", + "description": "Terrorism Act or Related Offence" + }, + "description": "Terrorism Act or Related Offence", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": true, + "createdAt": "2023-03-20T14:13:55", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + }, + { + "alertUuid": "f32a8c22-4ed5-4039-a497-4db0dd1f2406", + "prisonNumber": "A1351DZ", + "alertCode": { + "alertTypeCode": "X", + "alertTypeDescription": "Security", + "code": "XA", + "description": "Arsonist" + }, + "description": "Arsonist", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": true, + "createdAt": "2023-03-20T14:13:26", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + }, + { + "alertUuid": "b1e255ec-a39a-4ed6-aa81-3d6d9e99fa9e", + "prisonNumber": "A1351DZ", + "alertCode": { + "alertTypeCode": "R", + "alertTypeDescription": "Risk", + "code": "RNO121", + "description": "No 1 to 1 with this prisoner" + }, + "description": "1-1", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": true, + "createdAt": "2023-03-20T14:12:47", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + }, + { + "alertUuid": "b6f8c9eb-2adf-41e5-9427-e9b5af237bfe", + "prisonNumber": "A1351DZ", + "alertCode": { + "alertTypeCode": "R", + "alertTypeDescription": "Risk", + "code": "XCO", + "description": "Corruptor" + }, + "description": "Corruptor", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": true, + "createdAt": "2023-03-20T14:11:30", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + } + ] +} diff --git a/integration_tests/fixtures/alertsApi/getPrisonerAlertsA8644DY.json b/integration_tests/fixtures/alertsApi/getPrisonerAlertsA8644DY.json new file mode 100644 index 000000000..bdea6871f --- /dev/null +++ b/integration_tests/fixtures/alertsApi/getPrisonerAlertsA8644DY.json @@ -0,0 +1,28 @@ +{ + "content": [ + { + "alertUuid": "6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa", + "prisonNumber": "A8644DY", + "alertCode": { + "alertTypeCode": "X", + "alertTypeDescription": "Security", + "code": "XNR", + "description": "Not For Release" + }, + "description": "Not For Release", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": true, + "createdAt": "2023-03-20T14:14:27", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + } + ] +} diff --git a/integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GW.json b/integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GW.json new file mode 100644 index 000000000..c8fde8bc8 --- /dev/null +++ b/integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GW.json @@ -0,0 +1,28 @@ +{ + "content": [ + { + "alertUuid": "6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa", + "prisonNumber": "G0995GW", + "alertCode": { + "alertTypeCode": "X", + "alertTypeDescription": "Security", + "code": "XNR", + "description": "Not For Release" + }, + "description": "Not For Release", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": true, + "createdAt": "2023-03-20T14:14:27", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + } + ] +} diff --git a/integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GWG6123VU.json b/integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GWG6123VU.json new file mode 100644 index 000000000..c8fde8bc8 --- /dev/null +++ b/integration_tests/fixtures/alertsApi/getPrisonerAlertsG0995GWG6123VU.json @@ -0,0 +1,28 @@ +{ + "content": [ + { + "alertUuid": "6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa", + "prisonNumber": "G0995GW", + "alertCode": { + "alertTypeCode": "X", + "alertTypeDescription": "Security", + "code": "XNR", + "description": "Not For Release" + }, + "description": "Not For Release", + "authorisedBy": null, + "activeFrom": "2023-03-20", + "activeTo": null, + "isActive": true, + "createdAt": "2023-03-20T14:14:27", + "createdBy": "SCH_ACTIVITY", + "createdByDisplayName": "Schedule Activity", + "lastModifiedAt": null, + "lastModifiedBy": null, + "lastModifiedByDisplayName": null, + "activeToLastSetAt": null, + "activeToLastSetBy": null, + "activeToLastSetByDisplayName": null + } + ] +} diff --git a/server/data/alertsApiClient.ts b/server/data/alertsApiClient.ts index 7e6e91a71..e356e4f6e 100644 --- a/server/data/alertsApiClient.ts +++ b/server/data/alertsApiClient.ts @@ -8,7 +8,7 @@ export default class AlertsApiClient extends AbstractHmppsRestClient { super('Alerts API', config.apis.alertsApi as ApiConfig) } - async getAlertsForPrisoners(prisonerNumbers: string[], user: ServiceUser): Promise { + async getAlertsForPrisoners(prisonerNumbers: string[], user: ServiceUser): Promise<{ content: Alert[] }> { return this.post( { path: `/search/alerts/prison-numbers`, diff --git a/server/data/prisonApiClient.ts b/server/data/prisonApiClient.ts index 72547bcd7..7463a8be8 100644 --- a/server/data/prisonApiClient.ts +++ b/server/data/prisonApiClient.ts @@ -2,7 +2,7 @@ import { Readable } from 'stream' import config, { ApiConfig } from '../config' import AbstractHmppsRestClient from './abstractHmppsRestClient' -import { ReferenceCode, AgencyPrisonerPayProfile, Alert } from '../@types/prisonApiImport/types' +import { ReferenceCode, AgencyPrisonerPayProfile } from '../@types/prisonApiImport/types' import { ServiceUser } from '../@types/express' import { LocationLenient } from '../@types/prisonApiImportCustom' @@ -31,15 +31,6 @@ export default class PrisonApiClient extends AbstractHmppsRestClient { }) } - // TODO: Move to prisonSearchApiClient once alerts endpoint is available - async getPrisonersAlerts(offenderNumbers: string[], prisonCode: string, user: ServiceUser): Promise { - return this.post({ - path: `/api/bookings/offenderNo/${prisonCode}/alerts`, - data: offenderNumbers, - authToken: user.token, - }) - } - async getInternalLocationByKey(key: string, user: ServiceUser): Promise { return this.get({ path: `/api/locations/code/${key}`, authToken: user.token }) } diff --git a/server/routes/appointments/create-and-edit/createRoutes.ts b/server/routes/appointments/create-and-edit/createRoutes.ts index 7b7cff290..ff604dfab 100644 --- a/server/routes/appointments/create-and-edit/createRoutes.ts +++ b/server/routes/appointments/create-and-edit/createRoutes.ts @@ -35,7 +35,6 @@ import CopySeries, { HowToCopySeriesForm } from './handlers/copySeries' import NoAttendees from './handlers/noAttendees' import ReviewPrisonersAlertsRoutes from './handlers/reviewPrisonersAlerts' import ReviewNonAssociationsRoutes from './handlers/reviewNonAssociations' -import PrisonerAlertsService from '../../../services/prisonerAlertsService' import fetchAppointmentSeries from '../../../middleware/appointments/fetchAppointmentSeries' import AppointeeAttendeeService from '../../../services/appointeeAttendeeService' import ConfirmNonAssociationRoutes from './handlers/confirmNonAssociations' @@ -45,6 +44,7 @@ export default function Create({ activitiesService, metricsService, nonAssociationsService, + alertsService, }: Services): Router { const router = Router({ mergeParams: true }) @@ -54,7 +54,6 @@ export default function Create({ router.post(path, validationMiddleware(type), asyncMiddleware(handler)) const editAppointmentService = new EditAppointmentService(activitiesService, metricsService) - const prisonerAlertsService = new PrisonerAlertsService(prisonService) const appointeeAttendeeService = new AppointeeAttendeeService(prisonService) const startJourneyRoutes = new StartJourneyRoutes(prisonService, metricsService, appointeeAttendeeService) const selectPrisonerRoutes = new SelectPrisonerRoutes(prisonService) @@ -72,12 +71,12 @@ export default function Create({ const checkAnswersRoutes = new CheckAnswersRoutes(activitiesService) const confirmationRoutes = new ConfirmationRoutes(metricsService) const howToAddPrisonerRoutes = new HowToAddPrisonerRoutes() - const reviewPrisonerRoutes = new ReviewPrisonerRoutes(metricsService, prisonerAlertsService) + const reviewPrisonerRoutes = new ReviewPrisonerRoutes(metricsService, alertsService) const appointmentSetUploadRoutes = new AppointmentSetUploadRoutes(new PrisonerListCsvParser(), prisonService) const appointmentSetDateRoutes = new AppointmentSetDateRoutes() const appointmentSetTimesRoutes = new AppointmentSetTimesRoutes() const scheduleRoutes = new ScheduleRoutes(activitiesService, editAppointmentService, metricsService) - const reviewPrisonerAlerts = new ReviewPrisonersAlertsRoutes(prisonerAlertsService) + const reviewPrisonerAlerts = new ReviewPrisonersAlertsRoutes(alertsService) const reviewNonAssociations = new ReviewNonAssociationsRoutes(nonAssociationsService, prisonService) const confirmNonAssociations = new ConfirmNonAssociationRoutes(nonAssociationsService) const copySeriesRoutes = new CopySeries() diff --git a/server/routes/appointments/create-and-edit/editRoutes.ts b/server/routes/appointments/create-and-edit/editRoutes.ts index 44a3d8dfd..03e6fba13 100644 --- a/server/routes/appointments/create-and-edit/editRoutes.ts +++ b/server/routes/appointments/create-and-edit/editRoutes.ts @@ -23,7 +23,6 @@ import CancellationReasonRoutes, { CancellationReason } from './handlers/cancell import TierRoutes, { TierForm } from './handlers/tier' import HostRoutes, { HostForm } from './handlers/host' import ReviewPrisonersAlertsRoutes from './handlers/reviewPrisonersAlerts' -import PrisonerAlertsService from '../../../services/prisonerAlertsService' import AppointeeAttendeeService from '../../../services/appointeeAttendeeService' import UncancelRoutes from './handlers/uncancel' import ReviewNonAssociationRoutes from './handlers/reviewNonAssociations' @@ -33,6 +32,7 @@ export default function Edit({ activitiesService, metricsService, nonAssociationsService, + alertsService, }: Services): Router { const router = Router({ mergeParams: true }) @@ -44,7 +44,6 @@ export default function Edit({ const editAppointmentService = new EditAppointmentService(activitiesService, metricsService) const appointeeAttendeeService = new AppointeeAttendeeService(prisonService) const startJourneyRoutes = new StartJourneyRoutes(prisonService, metricsService, appointeeAttendeeService) - const prisonerAlertsService = new PrisonerAlertsService(prisonService) const tierRoutes = new TierRoutes(editAppointmentService) const hostRoutes = new HostRoutes(editAppointmentService) const locationRoutes = new LocationRoutes(activitiesService, editAppointmentService) @@ -124,8 +123,8 @@ export default function Edit({ const howToAddPrisoners = new HowToAddPrisoners() const selectPrisonerHandler = new SelectPrisonerRoutes(prisonService) const uploadPrisonerListRoutes = new UploadPrisonerListRoutes(new PrisonerListCsvParser(), prisonService) - const reviewPrisoners = new ReviewPrisoners(metricsService, prisonerAlertsService) - const reviewPrisonerAlerts = new ReviewPrisonersAlertsRoutes(prisonerAlertsService) + const reviewPrisoners = new ReviewPrisoners(metricsService, alertsService) + const reviewPrisonerAlerts = new ReviewPrisonersAlertsRoutes(alertsService) router.get( '/start/prisoners/add', diff --git a/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.test.ts b/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.test.ts index 5b9a0d7a7..bf4ec01c9 100644 --- a/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.test.ts +++ b/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.test.ts @@ -4,17 +4,17 @@ import ReviewPrisoners from './reviewPrisoners' import { AppointmentJourneyMode, AppointmentType } from '../appointmentJourney' import MetricsService from '../../../../services/metricsService' import MetricsEvent from '../../../../data/metricsEvent' -import PrisonerAlertsService, { PrisonerAlertResults } from '../../../../services/prisonerAlertsService' import { AppointmentPrisonerDetails } from '../appointmentPrisonerDetails' +import AlertsService, { PrisonerAlertResults } from '../../../../services/alertsService' jest.mock('../../../../services/metricsService') -jest.mock('../../../../services/prisonerAlertsService') +jest.mock('../../../../services/alertsService') const metricsService = new MetricsService(null) as jest.Mocked -const prisonerAlertsService = new PrisonerAlertsService(null) as jest.Mocked +const alertsService = new AlertsService(null) as jest.Mocked describe('Route Handlers - Create Appointment - Review Prisoners', () => { - const handler = new ReviewPrisoners(metricsService, prisonerAlertsService) + const handler = new ReviewPrisoners(metricsService, alertsService) let req: Request let res: Response const appointmentId = '2' @@ -185,8 +185,8 @@ describe('Route Handlers - Create Appointment - Review Prisoners', () => { async ({ mode }) => { req.session.appointmentJourney.mode = mode - when(prisonerAlertsService.getAlertDetails) - .calledWith(req.session.appointmentJourney.prisoners, res.locals.user.activeCaseLoadId, res.locals.user) + when(alertsService.getAlertDetails) + .calledWith(req.session.appointmentJourney.prisoners, res.locals.user) .mockReturnValue(Promise.resolve({ numPrisonersWithAlerts: 1 } as PrisonerAlertResults)) await handler.POST(req, res) @@ -195,8 +195,8 @@ describe('Route Handlers - Create Appointment - Review Prisoners', () => { ) it('should redirect or return to review alerts page there are no alerts when mode is CREATE', async () => { - when(prisonerAlertsService.getAlertDetails) - .calledWith(req.session.appointmentJourney.prisoners, res.locals.user.activeCaseLoadId, res.locals.user) + when(alertsService.getAlertDetails) + .calledWith(req.session.appointmentJourney.prisoners, res.locals.user) .mockReturnValue(Promise.resolve({ numPrisonersWithAlerts: 0 } as PrisonerAlertResults)) await handler.POST(req, res) @@ -206,8 +206,8 @@ describe('Route Handlers - Create Appointment - Review Prisoners', () => { it('should redirect or return to name page when there are no alerts when mode is COPY', async () => { req.session.appointmentJourney.mode = AppointmentJourneyMode.COPY - when(prisonerAlertsService.getAlertDetails) - .calledWith(req.session.appointmentJourney.prisoners, res.locals.user.activeCaseLoadId, res.locals.user) + when(alertsService.getAlertDetails) + .calledWith(req.session.appointmentJourney.prisoners, res.locals.user) .mockReturnValue(Promise.resolve({ numPrisonersWithAlerts: 0 } as PrisonerAlertResults)) await handler.POST(req, res) @@ -245,8 +245,8 @@ describe('Route Handlers - Create Appointment - Review Prisoners', () => { }) it('should redirect or return to review alerts page if there are any alerts', async () => { - when(prisonerAlertsService.getAlertDetails) - .calledWith(prisoners, res.locals.user.activeCaseLoadId, res.locals.user) + when(alertsService.getAlertDetails) + .calledWith(prisoners, res.locals.user) .mockReturnValue(Promise.resolve({ numPrisonersWithAlerts: 1 } as PrisonerAlertResults)) await handler.EDIT(req, res) @@ -255,8 +255,8 @@ describe('Route Handlers - Create Appointment - Review Prisoners', () => { }) it('should redirect to the alerts page if there are no alerts', async () => { - when(prisonerAlertsService.getAlertDetails) - .calledWith(prisoners, res.locals.user.activeCaseLoadId, res.locals.user) + when(alertsService.getAlertDetails) + .calledWith(prisoners, res.locals.user) .mockReturnValue(Promise.resolve({ numPrisonersWithAlerts: 0 } as PrisonerAlertResults)) await handler.EDIT(req, res) diff --git a/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.ts b/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.ts index ef070fb36..d6a509a20 100644 --- a/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.ts +++ b/server/routes/appointments/create-and-edit/handlers/reviewPrisoners.ts @@ -3,13 +3,13 @@ import { AppointmentJourneyMode, AppointmentType } from '../appointmentJourney' import config from '../../../../config' import MetricsService from '../../../../services/metricsService' import MetricsEvent from '../../../../data/metricsEvent' -import PrisonerAlertsService from '../../../../services/prisonerAlertsService' import { AppointmentPrisonerDetails } from '../appointmentPrisonerDetails' +import AlertsService from '../../../../services/alertsService' export default class ReviewPrisonerRoutes { constructor( private readonly metricsService: MetricsService, - private readonly prisonerAlertsService: PrisonerAlertsService, + private readonly alertsService: AlertsService, ) {} GET = async (req: Request, res: Response): Promise => { @@ -60,15 +60,10 @@ export default class ReviewPrisonerRoutes { } private async getNextPageInJourney(req: Request, res: Response): Promise { - const prisonCode = res.locals.user.activeCaseLoadId const prisoners = ReviewPrisonerRoutes.getPrisoners(req) - const prisonerAlertsDetails = await this.prisonerAlertsService.getAlertDetails( - prisoners, - prisonCode, - res.locals.user, - ) + const alertsDetails = await this.alertsService.getAlertDetails(prisoners, res.locals.user) - if (prisonerAlertsDetails.numPrisonersWithAlerts === 0) { + if (alertsDetails.numPrisonersWithAlerts === 0) { if (req.session.appointmentJourney.mode === AppointmentJourneyMode.EDIT) { return '../../schedule' } diff --git a/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.test.ts b/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.test.ts index 1a298b79c..2e7185e2a 100644 --- a/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.test.ts +++ b/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.test.ts @@ -2,14 +2,14 @@ import { Request, Response } from 'express' import { when } from 'jest-when' import { AppointmentJourneyMode, AppointmentType } from '../appointmentJourney' import ReviewPrisonersAlertsRoutes from './reviewPrisonersAlerts' -import PrisonerAlertsService, { PrisonerAlertResults } from '../../../../services/prisonerAlertsService' +import AlertsService, { PrisonerAlertResults } from '../../../../services/alertsService' -jest.mock('../../../../services/prisonerAlertsService') +jest.mock('../../../../services/alertsService') -const prisonerAlertsService = new PrisonerAlertsService(null) as jest.Mocked +const alertsService = new AlertsService(null) as jest.Mocked describe('Route Handlers - Create Appointment - Review Prisoners Alerts', () => { - const handler = new ReviewPrisonersAlertsRoutes(prisonerAlertsService) + const handler = new ReviewPrisonersAlertsRoutes(alertsService) let req: Request let res: Response @@ -51,12 +51,12 @@ describe('Route Handlers - Create Appointment - Review Prisoners Alerts', () => }) describe('GET', () => { - const prisonerAlertsDetails = {} as PrisonerAlertResults + const alertsDetails = {} as PrisonerAlertResults beforeEach(() => { - when(prisonerAlertsService.getAlertDetails) - .calledWith(req.session.appointmentJourney.prisoners, res.locals.user.activeCaseLoadId, res.locals.user) - .mockReturnValue(Promise.resolve(prisonerAlertsDetails)) + when(alertsService.getAlertDetails) + .calledWith(req.session.appointmentJourney.prisoners, res.locals.user) + .mockReturnValue(Promise.resolve(alertsDetails)) }) it('should render the view for create appointment', async () => { @@ -66,7 +66,7 @@ describe('Route Handlers - Create Appointment - Review Prisoners Alerts', () => appointmentId, backLinkHref: 'how-to-add-prisoners', preserveHistory, - prisonerAlertsDetails, + alertsDetails, }) }) @@ -79,7 +79,7 @@ describe('Route Handlers - Create Appointment - Review Prisoners Alerts', () => appointmentId, backLinkHref: 'upload-appointment-set', preserveHistory, - prisonerAlertsDetails, + alertsDetails, }) }) @@ -92,7 +92,7 @@ describe('Route Handlers - Create Appointment - Review Prisoners Alerts', () => appointmentId, backLinkHref: 'how-to-add-prisoners', preserveHistory, - prisonerAlertsDetails, + alertsDetails, }) }) @@ -105,7 +105,7 @@ describe('Route Handlers - Create Appointment - Review Prisoners Alerts', () => appointmentId, backLinkHref: 'https://digital-dev.prison.service.justice.gov.uk/prisoner/A1234BC', preserveHistory, - prisonerAlertsDetails, + alertsDetails, }) }) @@ -118,7 +118,7 @@ describe('Route Handlers - Create Appointment - Review Prisoners Alerts', () => appointmentId, backLinkHref: 'how-to-add-prisoners', preserveHistory: 'true', - prisonerAlertsDetails, + alertsDetails, }) }) }) diff --git a/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.ts b/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.ts index 6ddf6d880..f1bc86b53 100644 --- a/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.ts +++ b/server/routes/appointments/create-and-edit/handlers/reviewPrisonersAlerts.ts @@ -1,10 +1,10 @@ import { Request, Response } from 'express' import { AppointmentJourneyMode, AppointmentType } from '../appointmentJourney' import config from '../../../../config' -import PrisonerAlertsService from '../../../../services/prisonerAlertsService' +import AlertsService from '../../../../services/alertsService' export default class ReviewPrisonersAlertsRoutes { - constructor(private readonly prisonerAlertsService: PrisonerAlertsService) {} + constructor(private readonly alertsService: AlertsService) {} GET = async (req: Request, res: Response): Promise => { const { appointmentId } = req.params @@ -27,17 +27,13 @@ export default class ReviewPrisonersAlertsRoutes { prisoners = appointmentJourney.prisoners } - const prisonerAlertsDetails = await this.prisonerAlertsService.getAlertDetails( - prisoners, - res.locals.user.activeCaseLoadId, - res.locals.user, - ) + const alertsDetails = await this.alertsService.getAlertDetails(prisoners, res.locals.user) res.render('pages/appointments/create-and-edit/review-prisoners-alerts', { appointmentId, backLinkHref, preserveHistory, - prisonerAlertsDetails, + alertsDetails, }) } diff --git a/server/services/alertsService.test.ts b/server/services/alertsService.test.ts index a76b3e1dd..d0b7cf99c 100644 --- a/server/services/alertsService.test.ts +++ b/server/services/alertsService.test.ts @@ -3,19 +3,110 @@ import atLeast from '../../jest.setup' import { ServiceUser } from '../@types/express' import AlertsApiClient from '../data/alertsApiClient' -import AlertsService from './alertsService' +import AlertsService, { PrisonerAlertDetails, PrisonerAlertResults, PrisonerDetails } from './alertsService' import { Alert } from '../@types/alertsAPI/types' jest.mock('../data/alertsApiClient') describe('Alerts service', () => { - const alertsApiClient = new AlertsApiClient() - const alertsService = new AlertsService(alertsApiClient) + let alertsApiClient: AlertsApiClient + let alertsService: AlertsService const user = { activeCaseLoadId: 'MDI', } as ServiceUser + let prisonerMaggie: PrisonerDetails + let prisonerDave: PrisonerDetails + + let tectAlertForMaggie: Partial + let xcuAlertForMaggie: Partial + let peepAlertForDave: Partial + + let maggieResult: PrisonerAlertDetails + let daveResult: PrisonerAlertDetails + + beforeEach(() => { + alertsApiClient = new AlertsApiClient() + alertsService = new AlertsService(alertsApiClient) + + prisonerMaggie = { + number: 'B1111B', + name: 'Maggie Jones', + category: 'H', + } + + prisonerDave = { + number: 'A1111A', + name: 'Dave Smith', + category: 'A', + } + + tectAlertForMaggie = { + prisonNumber: 'B1111B', + isActive: true, + alertCode: { + alertTypeCode: '', + alertTypeDescription: '', + code: 'TECT', + description: 'Terrorism Act or Related Offence', + }, + } + + xcuAlertForMaggie = { + prisonNumber: 'B1111B', + isActive: true, + alertCode: { + alertTypeCode: '', + alertTypeDescription: '', + code: 'XCU', + description: 'Controlled Unlock', + }, + } + + peepAlertForDave = { + prisonNumber: 'A1111A', + isActive: true, + alertCode: { + alertTypeCode: '', + alertTypeDescription: '', + code: 'PEEP', + description: 'Personal Emergency Evacuation Plan', + }, + } + + maggieResult = { + number: prisonerMaggie.number, + name: prisonerMaggie.name, + category: prisonerMaggie.category, + alerts: [ + { + alertCode: tectAlertForMaggie.alertCode.code, + }, + { + alertCode: xcuAlertForMaggie.alertCode.code, + }, + ], + alertDescriptions: [xcuAlertForMaggie.alertCode.description, tectAlertForMaggie.alertCode.description], + hasBadgeAlerts: true, + hasRelevantCategories: true, + } + + daveResult = { + number: prisonerDave.number, + name: prisonerDave.name, + category: prisonerDave.category, + alerts: [ + { + alertCode: peepAlertForDave.alertCode.code, + }, + ], + alertDescriptions: [peepAlertForDave.alertCode.description], + hasBadgeAlerts: true, + hasRelevantCategories: true, + } + }) + describe('getAlertsForPrisoners', () => { it('should respond with the alerts for the prisoners given, from the alerts API', async () => { const apiResponse: Alert[] = [ @@ -47,12 +138,312 @@ describe('Alerts service', () => { const prisonerNumbers = ['G4793VF', 'Q6755TY'] - when(alertsApiClient.getAlertsForPrisoners).calledWith(atLeast(prisonerNumbers)).mockResolvedValue(apiResponse) + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(prisonerNumbers)) + .mockResolvedValue({ content: apiResponse }) const actualResult = await alertsService.getAlertsUsingPrisonerNumbers(prisonerNumbers, user) expect(actualResult).toEqual(apiResponse) expect(alertsApiClient.getAlertsForPrisoners).toHaveBeenCalledWith(prisonerNumbers, user) }) + it('filters out inactive alerts', async () => { + const apiResponse: Alert[] = [ + { + alertUuid: '6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa', + prisonNumber: 'G4793VF', + alertCode: { + alertTypeCode: 'X', + alertTypeDescription: 'Security', + code: 'XTACT', + description: 'Terrorism Act or Related Offence', + }, + description: 'TACT', + authorisedBy: 'TEST_GEN', + activeFrom: '2023-03-20', + activeTo: '2026-03-20', + isActive: true, + createdAt: '2023-03-20T14:14:27', + createdBy: 'SCH_ACTIVITY', + createdByDisplayName: 'Schedule Activity', + lastModifiedAt: '2023-07-20T14:14:27', + lastModifiedBy: 'TEST_GEN', + lastModifiedByDisplayName: 'TESTER', + activeToLastSetAt: '2023-07-20T14:14:27', + activeToLastSetBy: 'TEST_GEN', + activeToLastSetByDisplayName: 'TESTER', + }, + { + alertUuid: '2', + prisonNumber: 'G4793VF', + alertCode: { + alertTypeCode: 'X', + alertTypeDescription: 'Security', + code: 'XTACT', + description: 'Terrorism Act or Related Offence', + }, + description: 'TACT', + authorisedBy: 'TEST_GEN', + activeFrom: '2023-03-20', + activeTo: '2026-03-20', + isActive: false, + createdAt: '2023-03-20T14:14:27', + createdBy: 'SCH_ACTIVITY', + createdByDisplayName: 'Schedule Activity', + lastModifiedAt: '2023-07-20T14:14:27', + lastModifiedBy: 'TEST_GEN', + lastModifiedByDisplayName: 'TESTER', + activeToLastSetAt: '2023-07-20T14:14:27', + activeToLastSetBy: 'TEST_GEN', + activeToLastSetByDisplayName: 'TESTER', + }, + ] + + const prisonerNumbers = ['G4793VF', 'Q6755TY'] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(prisonerNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResult = await alertsService.getAlertsUsingPrisonerNumbers(prisonerNumbers, user) + + expect(actualResult).toEqual([apiResponse[0]]) + expect(alertsApiClient.getAlertsForPrisoners).toHaveBeenCalledWith(prisonerNumbers, user) + }) + }) + describe('getAlertDetails', () => { + it('should return alerts when all alerts from API are matched', async () => { + const prisoners = [prisonerDave, prisonerMaggie] + + const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) + + const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as unknown as Alert[] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(expectedOffenderNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResults = await alertsService.getAlertDetails(prisoners, user) + + const expectedResults = { + numPrisonersWithAlerts: 2, + prisoners: [maggieResult, daveResult], + } + + expect(actualResults).toEqual(expectedResults) + }) + it('should return alerts ignoring expired alerts and where category is relevant', async () => { + const prisoners = [prisonerDave, prisonerMaggie] + + const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) + + tectAlertForMaggie.isActive = false + + const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as Alert[] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(expectedOffenderNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResults = await alertsService.getAlertDetails(prisoners, user) + + maggieResult.alerts = [{ alertCode: xcuAlertForMaggie.alertCode.code }] + maggieResult.alertDescriptions = [xcuAlertForMaggie.alertCode.description] + + const expectedResults = { + numPrisonersWithAlerts: 2, + prisoners: [daveResult, maggieResult], + } + + expect(actualResults).toEqual(expectedResults) + }) + it('should return alerts ignoring inactive alerts and where category is relevant', async () => { + const prisoners = [prisonerDave, prisonerMaggie] + + const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) + + tectAlertForMaggie.isActive = false + + const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as Alert[] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(expectedOffenderNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResults = await alertsService.getAlertDetails(prisoners, user) + + maggieResult.alerts = [{ alertCode: xcuAlertForMaggie.alertCode.code }] + maggieResult.alertDescriptions = [xcuAlertForMaggie.alertCode.description] + + const expectedResults = { + numPrisonersWithAlerts: 2, + prisoners: [daveResult, maggieResult], + } + + expect(actualResults).toEqual(expectedResults) + }) + it('should return alerts ignoring expired alerts and where category is irrelevant', async () => { + const prisoners = [prisonerDave, prisonerMaggie] + + const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) + + prisonerMaggie.category = 'X' + tectAlertForMaggie.isActive = false + + const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as Alert[] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(expectedOffenderNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResults = await alertsService.getAlertDetails(prisoners, user) + + maggieResult.category = prisonerMaggie.category + maggieResult.alerts = [{ alertCode: xcuAlertForMaggie.alertCode.code }] + maggieResult.alertDescriptions = [xcuAlertForMaggie.alertCode.description] + maggieResult.hasBadgeAlerts = true + maggieResult.hasRelevantCategories = false + + const expectedResults = { + numPrisonersWithAlerts: 2, + prisoners: [daveResult, maggieResult], + } + + expect(actualResults).toEqual(expectedResults) + }) + it('should return alerts where an alert is irrelevant and category is irrelevant', async () => { + const prisoners = [prisonerDave, prisonerMaggie] + + const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) + + prisonerMaggie.category = 'X' + + const apiResponse = [peepAlertForDave, tectAlertForMaggie] as unknown as Alert[] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(expectedOffenderNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResults = await alertsService.getAlertDetails(prisoners, user) + + maggieResult.category = prisonerMaggie.category + maggieResult.alerts = [{ alertCode: tectAlertForMaggie.alertCode.code }] + maggieResult.alertDescriptions = [tectAlertForMaggie.alertCode.description] + maggieResult.hasBadgeAlerts = false + maggieResult.hasRelevantCategories = false + + const expectedResults = { + numPrisonersWithAlerts: 2, + prisoners: [daveResult, maggieResult], + } + + expect(actualResults).toEqual(expectedResults) + }) + it('should return alerts when there are no alerts', async () => { + const prisoners = [prisonerDave, prisonerMaggie] + + const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) + + prisonerDave.category = 'X' + prisonerMaggie.category = 'X' + peepAlertForDave.isActive = false + tectAlertForMaggie.isActive = false + + const apiResponse = [peepAlertForDave, tectAlertForMaggie] as unknown as Alert[] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(expectedOffenderNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResults = await alertsService.getAlertDetails(prisoners, user) + + daveResult.category = prisonerMaggie.category + daveResult.alerts = [] + daveResult.alertDescriptions = [] + daveResult.hasBadgeAlerts = false + daveResult.hasRelevantCategories = false + maggieResult.category = prisonerMaggie.category + maggieResult.alerts = [] + maggieResult.alertDescriptions = [] + maggieResult.hasBadgeAlerts = false + maggieResult.hasRelevantCategories = false + + const expectedResults = { + numPrisonersWithAlerts: 0, + prisoners: [daveResult, maggieResult], + } + + expect(actualResults).toEqual(expectedResults) + }) + it('should sort alerts correctly', async () => { + const prisonerAlan = { + number: 'D1111D', + name: 'Alan Jones', + category: 'A', + } + + const prisonerAlice = { + number: 'E1111E', + name: 'Alice Robins', + } + + const ocgAlertForAlice = { + prisonNumber: 'E1111E', + isActive: true, + alertCode: { + alertTypeCode: '', + alertTypeDescription: '', + code: 'OCG', + description: 'OCG Nominal', + }, + } + + const alanResult = { + number: prisonerAlan.number, + name: prisonerAlan.name, + category: prisonerAlan.category, + alerts: [] as PrisonerAlertResults[], + alertDescriptions: [] as string[], + hasBadgeAlerts: true, + hasRelevantCategories: true, + } + + const aliceResult = { + number: prisonerAlice.number, + name: prisonerAlice.name, + alerts: [ + { + alertCode: ocgAlertForAlice.alertCode.code, + }, + ], + alertDescriptions: [ocgAlertForAlice.alertCode.description], + hasBadgeAlerts: false, + hasRelevantCategories: false, + } + + const prisoners = [prisonerAlice, prisonerDave, prisonerMaggie, prisonerAlan] + + const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) + + const apiResponse = [ + peepAlertForDave, + tectAlertForMaggie, + xcuAlertForMaggie, + ocgAlertForAlice, + ] as unknown as Alert[] + + when(alertsApiClient.getAlertsForPrisoners) + .calledWith(atLeast(expectedOffenderNumbers)) + .mockResolvedValue({ content: apiResponse }) + + const actualResults = await alertsService.getAlertDetails(prisoners, user) + + const expectedResults = { + numPrisonersWithAlerts: 3, + prisoners: [maggieResult, daveResult, aliceResult, alanResult], + } + + expect(actualResults).toEqual(expectedResults) + }) }) }) diff --git a/server/services/alertsService.ts b/server/services/alertsService.ts index a4516e542..f3314866a 100644 --- a/server/services/alertsService.ts +++ b/server/services/alertsService.ts @@ -5,7 +5,118 @@ import AlertsApiClient from '../data/alertsApiClient' export default class AlertsService { constructor(private readonly alertsApiClient: AlertsApiClient) {} + /* + We need to know which prisoner has badged alerts to determine how to display this text list + */ + readonly alertsWithBadges: Set = new Set([ + 'HA', + 'HA1', + 'XSA', + 'XA', + 'PEEP', + 'XEL', + 'XRF', + 'XTACT', + 'XCO', + 'XCA', + 'XCI', + 'XR', + 'RTP', + 'RLG', + 'XHT', + 'XCU', + 'XGANG', + 'CSIP', + 'F1', + 'LCE', + 'RNO121', + 'RCON', + 'RCDR', + 'URCU', + 'UPIU', + 'USU', + 'URS', + 'PVN', + 'SA', + 'RKS', + 'VIP', + 'HID', + ]) + + readonly categoriesWithBadges: Set = new Set(['A', 'E', 'H', 'P']) + async getAlertsUsingPrisonerNumbers(prisonerNumbers: string[], user: ServiceUser): Promise { - return this.alertsApiClient.getAlertsForPrisoners(prisonerNumbers, user) + return this.alertsApiClient + .getAlertsForPrisoners(prisonerNumbers, user) + .then(a => a.content.filter(alert => alert.isActive)) + } + + async getAlertDetails(prisoners: PrisonerDetails[], user: ServiceUser): Promise { + const prisonerNumbers: string[] = prisoners.map(prisoner => prisoner.number) + + const allAlertsForAllPrisoners: Alert[] = await this.getAlertsUsingPrisonerNumbers(prisonerNumbers, user) + + const prisonersWithAlerts: PrisonerAlertDetails[] = [] + let numPrisonersWithAlerts = 0 + + prisoners.forEach(prisoner => { + const relevantAlerts = allAlertsForAllPrisoners.filter(alert => alert.prisonNumber === prisoner.number) + + const prisonerAlerts = relevantAlerts.map(a => ({ alertCode: a.alertCode.code })) + + const prisonerAlertDescriptions = relevantAlerts + .map(alert => alert.alertCode.description) + .sort((a, b) => a.localeCompare(b)) + + const isCategoryRelevant = this.categoriesWithBadges.has(prisoner.category) + + if (prisonerAlerts.length > 0) { + numPrisonersWithAlerts += 1 + } + + prisonersWithAlerts.push({ + ...prisoner, + alerts: prisonerAlerts, + alertDescriptions: prisonerAlertDescriptions, + hasRelevantCategories: isCategoryRelevant, + hasBadgeAlerts: + isCategoryRelevant || relevantAlerts.some(alert => this.alertsWithBadges.has(alert.alertCode.code)), + }) + }) + + prisonersWithAlerts.sort((a, b) => { + if (a.alerts.length === b.alerts.length) { + if (a.hasBadgeAlerts === b.hasBadgeAlerts) { + return 0 + } + return a.hasBadgeAlerts ? -1 : 1 + } + return a.alerts.length > b.alerts.length ? -1 : 1 + }) + + return { + numPrisonersWithAlerts, + prisoners: prisonersWithAlerts, + } } } + +export type PrisonerDetails = { + number: string + name: string + category?: string +} + +export type PrisonerAlertDetails = PrisonerDetails & { + alerts: { + alertCode: string + }[] + alertDescriptions: string[] + hasBadgeAlerts: boolean + hasRelevantCategories: boolean +} + +export type PrisonerAlertResults = { + numPrisonersWithAlerts: number + prisoners: PrisonerDetails[] +} diff --git a/server/services/index.ts b/server/services/index.ts index 26bcd0ac7..3c7f30897 100644 --- a/server/services/index.ts +++ b/server/services/index.ts @@ -31,6 +31,7 @@ export default function services() { const alertsFilterService = new AlertsFilterService() const prisonService = new PrisonService(prisonApiClient, prisonerSearchApiClient, incentivesApiClient) + const alertsService = new AlertsService(alertsApiClient) return { userService: new UserService(manageUsersApiClient, prisonRegisterApiClient, activitiesApiClient), @@ -43,7 +44,7 @@ export default function services() { unlockListService: new UnlockListService(prisonerSearchApiClient, activitiesApiClient, alertsFilterService), metricsService: new MetricsService(applicationInsightsClient), nonAssociationsService: new NonAssociationsService(nonAssociationsApiClient, prisonService), - alertsService: new AlertsService(alertsApiClient), + alertsService, locationMappingService: new LocationMappingService(locationsInsidePrisonApiClient, nomisMappingClient), } } diff --git a/server/services/prisonService.ts b/server/services/prisonService.ts index 7c168772b..baee5feea 100644 --- a/server/services/prisonService.ts +++ b/server/services/prisonService.ts @@ -2,7 +2,7 @@ import _ from 'lodash' import { Readable } from 'stream' import PrisonApiClient from '../data/prisonApiClient' import PrisonerSearchApiClient from '../data/prisonerSearchApiClient' -import { ReferenceCode, AgencyPrisonerPayProfile, Alert } from '../@types/prisonApiImport/types' +import { ReferenceCode, AgencyPrisonerPayProfile } from '../@types/prisonApiImport/types' import { PagePrisoner, Prisoner } from '../@types/prisonerOffenderSearchImport/types' import { ServiceUser } from '../@types/express' @@ -66,10 +66,6 @@ export default class PrisonService { return this.prisonApiClient.getPayProfile(prisonCode) } - async getPrisonerAlerts(offenderNumbers: string[], prisonCode: string, user: ServiceUser): Promise { - return this.prisonApiClient.getPrisonersAlerts(offenderNumbers, prisonCode, user) - } - async getPrisonerImage(prisonerNumber: string, user: ServiceUser): Promise { return this.prisonApiClient.getPrisonerImage(prisonerNumber, user) } diff --git a/server/services/prisonerAlertsService.test.ts b/server/services/prisonerAlertsService.test.ts deleted file mode 100644 index 512db6e24..000000000 --- a/server/services/prisonerAlertsService.test.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { when } from 'jest-when' -import atLeast from '../../jest.setup' -import PrisonService from './prisonService' -import { ServiceUser } from '../@types/express' -import PrisonerAlertsService, { - PrisonerAlertDetails, - PrisonerAlertResults, - PrisonerDetails, -} from './prisonerAlertsService' -import { Alert } from '../@types/prisonApiImport/types' - -jest.mock('./prisonService') - -describe('Prisoner Alerts Service', () => { - let prisonService: PrisonService - let prisonerAlertsService: PrisonerAlertsService - - const user = { - activeCaseLoadId: 'MDI', - } as ServiceUser - - let prisonerMaggie: PrisonerDetails - let prisonerDave: PrisonerDetails - - let tectAlertForMaggie: Partial - let xcuAlertForMaggie: Partial - let peepAlertForDave: Partial - - let maggieResult: PrisonerAlertDetails - let daveResult: PrisonerAlertDetails - - beforeEach(() => { - prisonService = new PrisonService(null, null, null) as jest.Mocked - prisonerAlertsService = new PrisonerAlertsService(prisonService) - - prisonerMaggie = { - number: 'B1111B', - name: 'Maggie Jones', - category: 'H', - } - - prisonerDave = { - number: 'A1111A', - name: 'Dave Smith', - category: 'A', - } - - tectAlertForMaggie = { - offenderNo: 'B1111B', - active: true, - expired: false, - alertCode: 'TECT', - alertCodeDescription: 'Terrorism Act or Related Offence', - } - - xcuAlertForMaggie = { - offenderNo: 'B1111B', - active: true, - expired: false, - alertCode: 'XCU', - alertCodeDescription: 'Controlled Unlock', - } - - peepAlertForDave = { - offenderNo: 'A1111A', - active: true, - expired: false, - alertCode: 'PEEP', - alertCodeDescription: 'Personal Emergency Evacuation Plan', - } - - maggieResult = { - number: prisonerMaggie.number, - name: prisonerMaggie.name, - category: prisonerMaggie.category, - alerts: [ - { - alertCode: tectAlertForMaggie.alertCode, - }, - { - alertCode: xcuAlertForMaggie.alertCode, - }, - ], - alertDescriptions: [xcuAlertForMaggie.alertCodeDescription, tectAlertForMaggie.alertCodeDescription], - hasBadgeAlerts: true, - hasRelevantCategories: true, - } - - daveResult = { - number: prisonerDave.number, - name: prisonerDave.name, - category: prisonerDave.category, - alerts: [ - { - alertCode: peepAlertForDave.alertCode, - }, - ], - alertDescriptions: [peepAlertForDave.alertCodeDescription], - hasBadgeAlerts: true, - hasRelevantCategories: true, - } - }) - - it('should return alerts when all alerts from API are matched', async () => { - const prisoners = [prisonerDave, prisonerMaggie] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as unknown as Alert[] - - when(prisonService.getPrisonerAlerts) - .calledWith(atLeast(expectedOffenderNumbers, 'MDI', user)) - .mockResolvedValue(apiResponse) - - const actualResults = await prisonerAlertsService.getAlertDetails(prisoners, 'MDI', user) - - const expectedResults = { - numPrisonersWithAlerts: 2, - prisoners: [maggieResult, daveResult], - } - - expect(actualResults).toEqual(expectedResults) - }) - - it('should return alerts ignoring expired alerts and where category is relevant', async () => { - const prisoners = [prisonerDave, prisonerMaggie] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - tectAlertForMaggie.expired = true - - const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as unknown as Alert[] - - when(prisonService.getPrisonerAlerts) - .calledWith(atLeast(expectedOffenderNumbers, 'MDI', user)) - .mockReturnValue(Promise.resolve(apiResponse)) - - const actualResults = await prisonerAlertsService.getAlertDetails(prisoners, 'MDI', user) - - maggieResult.alerts = [{ alertCode: xcuAlertForMaggie.alertCode }] - maggieResult.alertDescriptions = [xcuAlertForMaggie.alertCodeDescription] - - const expectedResults = { - numPrisonersWithAlerts: 2, - prisoners: [daveResult, maggieResult], - } - - expect(actualResults).toEqual(expectedResults) - }) - - it('should return alerts ignoring inactive alerts and where category is relevant', async () => { - const prisoners = [prisonerDave, prisonerMaggie] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - tectAlertForMaggie.active = false - - const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as unknown as Alert[] - - when(prisonService.getPrisonerAlerts) - .calledWith(atLeast(expectedOffenderNumbers, 'MDI', user)) - .mockResolvedValue(apiResponse) - - const actualResults = await prisonerAlertsService.getAlertDetails(prisoners, 'MDI', user) - - maggieResult.alerts = [{ alertCode: xcuAlertForMaggie.alertCode }] - maggieResult.alertDescriptions = [xcuAlertForMaggie.alertCodeDescription] - - const expectedResults = { - numPrisonersWithAlerts: 2, - prisoners: [daveResult, maggieResult], - } - - expect(actualResults).toEqual(expectedResults) - }) - - it('should return alerts ignoring expired alerts and where category is irrelevant', async () => { - const prisoners = [prisonerDave, prisonerMaggie] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - prisonerMaggie.category = 'X' - tectAlertForMaggie.expired = true - - const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as unknown as Alert[] - - when(prisonService.getPrisonerAlerts) - .calledWith(atLeast(expectedOffenderNumbers, 'MDI', user)) - .mockResolvedValue(apiResponse) - - const actualResults = await prisonerAlertsService.getAlertDetails(prisoners, 'MDI', user) - - maggieResult.category = prisonerMaggie.category - maggieResult.alerts = [{ alertCode: xcuAlertForMaggie.alertCode }] - maggieResult.alertDescriptions = [xcuAlertForMaggie.alertCodeDescription] - maggieResult.hasBadgeAlerts = true - maggieResult.hasRelevantCategories = false - - const expectedResults = { - numPrisonersWithAlerts: 2, - prisoners: [daveResult, maggieResult], - } - - expect(actualResults).toEqual(expectedResults) - }) - - it('should return alerts where an alert is irrelevant and category is irrelevant', async () => { - const prisoners = [prisonerDave, prisonerMaggie] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - prisonerMaggie.category = 'X' - - const apiResponse = [peepAlertForDave, tectAlertForMaggie] as unknown as Alert[] - - when(prisonService.getPrisonerAlerts) - .calledWith(atLeast(expectedOffenderNumbers, 'MDI', user)) - .mockResolvedValue(apiResponse) - - const actualResults = await prisonerAlertsService.getAlertDetails(prisoners, 'MDI', user) - - maggieResult.category = prisonerMaggie.category - maggieResult.alerts = [{ alertCode: tectAlertForMaggie.alertCode }] - maggieResult.alertDescriptions = [tectAlertForMaggie.alertCodeDescription] - maggieResult.hasBadgeAlerts = false - maggieResult.hasRelevantCategories = false - - const expectedResults = { - numPrisonersWithAlerts: 2, - prisoners: [daveResult, maggieResult], - } - - expect(actualResults).toEqual(expectedResults) - }) - - it('should return alerts when there are no alerts', async () => { - const prisoners = [prisonerDave, prisonerMaggie] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - prisonerDave.category = 'X' - prisonerMaggie.category = 'X' - peepAlertForDave.expired = true - tectAlertForMaggie.active = false - - const apiResponse = [peepAlertForDave, tectAlertForMaggie] as unknown as Alert[] - - when(prisonService.getPrisonerAlerts) - .calledWith(atLeast(expectedOffenderNumbers, 'MDI', user)) - .mockResolvedValue(apiResponse) - - const actualResults = await prisonerAlertsService.getAlertDetails(prisoners, 'MDI', user) - - daveResult.category = prisonerMaggie.category - daveResult.alerts = [] - daveResult.alertDescriptions = [] - daveResult.hasBadgeAlerts = false - daveResult.hasRelevantCategories = false - maggieResult.category = prisonerMaggie.category - maggieResult.alerts = [] - maggieResult.alertDescriptions = [] - maggieResult.hasBadgeAlerts = false - maggieResult.hasRelevantCategories = false - - const expectedResults = { - numPrisonersWithAlerts: 0, - prisoners: [daveResult, maggieResult], - } - - expect(actualResults).toEqual(expectedResults) - }) - - it('should sort alerts correctly', async () => { - const prisonerAlan = { - number: 'D1111D', - name: 'Alan Jones', - category: 'A', - } - - const prisonerAlice = { - number: 'E1111E', - name: 'Alice Robins', - } - - const ocgAlertForAlice = { - offenderNo: 'E1111E', - active: true, - expired: false, - alertCode: 'OCG', - alertCodeDescription: 'OCG Nominal', - } - - const alanResult = { - number: prisonerAlan.number, - name: prisonerAlan.name, - category: prisonerAlan.category, - alerts: [] as PrisonerAlertResults[], - alertDescriptions: [] as string[], - hasBadgeAlerts: true, - hasRelevantCategories: true, - } - - const aliceResult = { - number: prisonerAlice.number, - name: prisonerAlice.name, - alerts: [ - { - alertCode: ocgAlertForAlice.alertCode, - }, - ], - alertDescriptions: [ocgAlertForAlice.alertCodeDescription], - hasBadgeAlerts: false, - hasRelevantCategories: false, - } - - const prisoners = [prisonerAlice, prisonerDave, prisonerMaggie, prisonerAlan] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - const apiResponse = [ - peepAlertForDave, - tectAlertForMaggie, - xcuAlertForMaggie, - ocgAlertForAlice, - ] as unknown as Alert[] - - when(prisonService.getPrisonerAlerts) - .calledWith(atLeast(expectedOffenderNumbers, 'MDI', user)) - .mockResolvedValue(apiResponse) - - const actualResults = await prisonerAlertsService.getAlertDetails(prisoners, 'MDI', user) - - const expectedResults = { - numPrisonersWithAlerts: 3, - prisoners: [maggieResult, daveResult, aliceResult, alanResult], - } - - expect(actualResults).toEqual(expectedResults) - }) -}) diff --git a/server/services/prisonerAlertsService.ts b/server/services/prisonerAlertsService.ts deleted file mode 100644 index 060e349c0..000000000 --- a/server/services/prisonerAlertsService.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Alert } from '../@types/prisonApiImport/types' -import { ServiceUser } from '../@types/express' -import PrisonService from './prisonService' - -export default class PrisonerAlertsService { - constructor(private readonly prisonService: PrisonService) {} - - /* - We need to know which prisoner has badged alerts to determine how to display this text list - */ - readonly alertsWithBadges: Set = new Set([ - 'HA', - 'HA1', - 'XSA', - 'XA', - 'PEEP', - 'XEL', - 'XRF', - 'XTACT', - 'XCO', - 'XCA', - 'XCI', - 'XR', - 'RTP', - 'RLG', - 'XHT', - 'XCU', - 'XGANG', - 'CSIP', - 'F1', - 'LCE', - 'RNO121', - 'RCON', - 'RCDR', - 'URCU', - 'UPIU', - 'USU', - 'URS', - 'PVN', - 'SA', - 'RKS', - 'VIP', - 'HID', - ]) - - readonly categoriesWithBadges: Set = new Set(['A', 'E', 'H', 'P']) - - private fetchRelevantPrisonerAlerts( - offenderNumbers: string[], - prisonCode: string, - user: ServiceUser, - ): Promise { - return this.prisonService - .getPrisonerAlerts(offenderNumbers, prisonCode, user) - .then(a => a.filter(alert => !alert.expired && alert.active)) - } - - async getAlertDetails( - prisoners: PrisonerDetails[], - prisonCode: string, - user: ServiceUser, - ): Promise { - const offenderNumbers: string[] = prisoners.map(prisoner => prisoner.number) - - const allAlertsForAllPrisoners: Alert[] = await this.fetchRelevantPrisonerAlerts(offenderNumbers, prisonCode, user) - - const prisonersWithAlerts: PrisonerAlertDetails[] = [] - let numPrisonersWithAlerts = 0 - - prisoners.forEach(prisoner => { - const relevantAlerts = allAlertsForAllPrisoners.filter(alert => alert.offenderNo === prisoner.number) - - const prisonerAlerts = relevantAlerts.map(a => ({ alertCode: a.alertCode })) - - const prisonerAlertDescriptions = relevantAlerts - .map(alert => alert.alertCodeDescription) - .sort((a, b) => a.localeCompare(b)) - - const isCategoryRelevant = this.categoriesWithBadges.has(prisoner.category) - - if (prisonerAlerts.length > 0) { - numPrisonersWithAlerts += 1 - } - - prisonersWithAlerts.push({ - ...prisoner, - alerts: prisonerAlerts, - alertDescriptions: prisonerAlertDescriptions, - hasRelevantCategories: isCategoryRelevant, - hasBadgeAlerts: isCategoryRelevant || relevantAlerts.some(alert => this.alertsWithBadges.has(alert.alertCode)), - }) - }) - - prisonersWithAlerts.sort((a, b) => { - if (a.alerts.length === b.alerts.length) { - if (a.hasBadgeAlerts === b.hasBadgeAlerts) { - return 0 - } - return a.hasBadgeAlerts ? -1 : 1 - } - return a.alerts.length > b.alerts.length ? -1 : 1 - }) - - return { - numPrisonersWithAlerts, - prisoners: prisonersWithAlerts, - } - } -} - -export type PrisonerDetails = { - number: string - name: string - category?: string -} - -export type PrisonerAlertDetails = PrisonerDetails & { - alerts: { - alertCode: string - }[] - alertDescriptions: string[] - hasBadgeAlerts: boolean - hasRelevantCategories: boolean -} - -export type PrisonerAlertResults = { - numPrisonersWithAlerts: number - prisoners: PrisonerDetails[] -} diff --git a/server/views/pages/appointments/create-and-edit/review-prisoners-alerts.njk b/server/views/pages/appointments/create-and-edit/review-prisoners-alerts.njk index 466f0aef8..95d7b4f27 100644 --- a/server/views/pages/appointments/create-and-edit/review-prisoners-alerts.njk +++ b/server/views/pages/appointments/create-and-edit/review-prisoners-alerts.njk @@ -12,8 +12,8 @@ {% from "moj/components/badge/macro.njk" import mojBadge -%} {% from "govuk/components/details/macro.njk" import govukDetails %} -{% set prisoners = prisonerAlertsDetails.prisoners %} -{% set numPrisonersWithAlerts = prisonerAlertsDetails.numPrisonersWithAlerts %} +{% set prisoners = alertsDetails.prisoners %} +{% set numPrisonersWithAlerts = alertsDetails.numPrisonersWithAlerts %} {% if session.appointmentJourney.mode == AppointmentJourneyMode.EDIT %} {% set pageTitle = appointmentJourneyTitle("no attendees added" if prisoners.length == 0 else "Review additional attendee" + ("s" if prisoners.length > 1), session.appointmentJourney) %} From 63e44aeae8b27316ec98d1ca788de97555aca86d Mon Sep 17 00:00:00 2001 From: Natalie Clamp Date: Thu, 9 Jan 2025 16:06:01 +0000 Subject: [PATCH 3/5] SAA-2272 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 7ef705e3a..6e266201c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ This service requires the following dependent services: ## Alerts -- Details of relevant alerts can be obtained from the `hmpps-prisoner-profile` repository [here](https://github.com/ministryofjustice/hmpps-prisoner-profile/blob/main/server/data/alertFlags/alertFlags.ts). - The icons for badges can be obtained from the `digital-prison-services` repository [here](https://github.com/ministryofjustice/digital-prison-services/tree/main/static/images). ## Running the application From 54b5022ddb532a948dc2b3a8186caf7bb006f9e4 Mon Sep 17 00:00:00 2001 From: Natalie Clamp Date: Thu, 9 Jan 2025 16:48:54 +0000 Subject: [PATCH 4/5] SAA-2272: Use flag to remove inactive alerts on api --- .../fixtures/alertsApi/getPrisonerAlerts.json | 24 ----- server/data/alertsApiClient.test.ts | 2 +- server/data/alertsApiClient.ts | 1 + server/services/alertsService.test.ts | 101 +----------------- server/services/alertsService.ts | 4 +- 5 files changed, 7 insertions(+), 125 deletions(-) diff --git a/integration_tests/fixtures/alertsApi/getPrisonerAlerts.json b/integration_tests/fixtures/alertsApi/getPrisonerAlerts.json index 70b8fd62b..7e224d4e4 100644 --- a/integration_tests/fixtures/alertsApi/getPrisonerAlerts.json +++ b/integration_tests/fixtures/alertsApi/getPrisonerAlerts.json @@ -1,29 +1,5 @@ { "content": [ - { - "alertUuid": "6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa", - "prisonNumber": "A1351DZ", - "alertCode": { - "alertTypeCode": "X", - "alertTypeDescription": "Security", - "code": "XNR", - "description": "Not For Release" - }, - "description": "Not For Release", - "authorisedBy": null, - "activeFrom": "2023-03-20", - "activeTo": null, - "isActive": false, - "createdAt": "2023-03-20T14:14:27", - "createdBy": "SCH_ACTIVITY", - "createdByDisplayName": "Schedule Activity", - "lastModifiedAt": null, - "lastModifiedBy": null, - "lastModifiedByDisplayName": null, - "activeToLastSetAt": null, - "activeToLastSetBy": null, - "activeToLastSetByDisplayName": null - }, { "alertUuid": "527b44f6-182e-4f5b-94c7-8f974bc77c64", "prisonNumber": "A1351DZ", diff --git a/server/data/alertsApiClient.test.ts b/server/data/alertsApiClient.test.ts index 083c737dc..074f72d97 100644 --- a/server/data/alertsApiClient.test.ts +++ b/server/data/alertsApiClient.test.ts @@ -29,7 +29,7 @@ describe('alertsApiClient', () => { const response = { data: 'some data' } fakeAlertsApi - .post('/search/alerts/prison-numbers') + .post('/search/alerts/prison-numbers?includeInactive=false') .matchHeader('authorization', `Bearer accessToken`) .reply(200, response) diff --git a/server/data/alertsApiClient.ts b/server/data/alertsApiClient.ts index e356e4f6e..f6d7d7870 100644 --- a/server/data/alertsApiClient.ts +++ b/server/data/alertsApiClient.ts @@ -12,6 +12,7 @@ export default class AlertsApiClient extends AbstractHmppsRestClient { return this.post( { path: `/search/alerts/prison-numbers`, + query: { includeInactive: false }, data: prisonerNumbers, }, user, diff --git a/server/services/alertsService.test.ts b/server/services/alertsService.test.ts index d0b7cf99c..9b30500e8 100644 --- a/server/services/alertsService.test.ts +++ b/server/services/alertsService.test.ts @@ -147,69 +147,6 @@ describe('Alerts service', () => { expect(actualResult).toEqual(apiResponse) expect(alertsApiClient.getAlertsForPrisoners).toHaveBeenCalledWith(prisonerNumbers, user) }) - it('filters out inactive alerts', async () => { - const apiResponse: Alert[] = [ - { - alertUuid: '6cd3d3cb-fe76-4746-b2ea-a6968a63c2aa', - prisonNumber: 'G4793VF', - alertCode: { - alertTypeCode: 'X', - alertTypeDescription: 'Security', - code: 'XTACT', - description: 'Terrorism Act or Related Offence', - }, - description: 'TACT', - authorisedBy: 'TEST_GEN', - activeFrom: '2023-03-20', - activeTo: '2026-03-20', - isActive: true, - createdAt: '2023-03-20T14:14:27', - createdBy: 'SCH_ACTIVITY', - createdByDisplayName: 'Schedule Activity', - lastModifiedAt: '2023-07-20T14:14:27', - lastModifiedBy: 'TEST_GEN', - lastModifiedByDisplayName: 'TESTER', - activeToLastSetAt: '2023-07-20T14:14:27', - activeToLastSetBy: 'TEST_GEN', - activeToLastSetByDisplayName: 'TESTER', - }, - { - alertUuid: '2', - prisonNumber: 'G4793VF', - alertCode: { - alertTypeCode: 'X', - alertTypeDescription: 'Security', - code: 'XTACT', - description: 'Terrorism Act or Related Offence', - }, - description: 'TACT', - authorisedBy: 'TEST_GEN', - activeFrom: '2023-03-20', - activeTo: '2026-03-20', - isActive: false, - createdAt: '2023-03-20T14:14:27', - createdBy: 'SCH_ACTIVITY', - createdByDisplayName: 'Schedule Activity', - lastModifiedAt: '2023-07-20T14:14:27', - lastModifiedBy: 'TEST_GEN', - lastModifiedByDisplayName: 'TESTER', - activeToLastSetAt: '2023-07-20T14:14:27', - activeToLastSetBy: 'TEST_GEN', - activeToLastSetByDisplayName: 'TESTER', - }, - ] - - const prisonerNumbers = ['G4793VF', 'Q6755TY'] - - when(alertsApiClient.getAlertsForPrisoners) - .calledWith(atLeast(prisonerNumbers)) - .mockResolvedValue({ content: apiResponse }) - - const actualResult = await alertsService.getAlertsUsingPrisonerNumbers(prisonerNumbers, user) - - expect(actualResult).toEqual([apiResponse[0]]) - expect(alertsApiClient.getAlertsForPrisoners).toHaveBeenCalledWith(prisonerNumbers, user) - }) }) describe('getAlertDetails', () => { it('should return alerts when all alerts from API are matched', async () => { @@ -232,39 +169,12 @@ describe('Alerts service', () => { expect(actualResults).toEqual(expectedResults) }) - it('should return alerts ignoring expired alerts and where category is relevant', async () => { + it('should return alerts where category is relevant', async () => { const prisoners = [prisonerDave, prisonerMaggie] const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - tectAlertForMaggie.isActive = false - - const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as Alert[] - - when(alertsApiClient.getAlertsForPrisoners) - .calledWith(atLeast(expectedOffenderNumbers)) - .mockResolvedValue({ content: apiResponse }) - - const actualResults = await alertsService.getAlertDetails(prisoners, user) - - maggieResult.alerts = [{ alertCode: xcuAlertForMaggie.alertCode.code }] - maggieResult.alertDescriptions = [xcuAlertForMaggie.alertCode.description] - - const expectedResults = { - numPrisonersWithAlerts: 2, - prisoners: [daveResult, maggieResult], - } - - expect(actualResults).toEqual(expectedResults) - }) - it('should return alerts ignoring inactive alerts and where category is relevant', async () => { - const prisoners = [prisonerDave, prisonerMaggie] - - const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) - - tectAlertForMaggie.isActive = false - - const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as Alert[] + const apiResponse = [peepAlertForDave, xcuAlertForMaggie] as Alert[] when(alertsApiClient.getAlertsForPrisoners) .calledWith(atLeast(expectedOffenderNumbers)) @@ -288,9 +198,8 @@ describe('Alerts service', () => { const expectedOffenderNumbers = prisoners.map(prisoner => prisoner.number) prisonerMaggie.category = 'X' - tectAlertForMaggie.isActive = false - const apiResponse = [peepAlertForDave, tectAlertForMaggie, xcuAlertForMaggie] as Alert[] + const apiResponse = [peepAlertForDave, xcuAlertForMaggie] as Alert[] when(alertsApiClient.getAlertsForPrisoners) .calledWith(atLeast(expectedOffenderNumbers)) @@ -346,10 +255,8 @@ describe('Alerts service', () => { prisonerDave.category = 'X' prisonerMaggie.category = 'X' - peepAlertForDave.isActive = false - tectAlertForMaggie.isActive = false - const apiResponse = [peepAlertForDave, tectAlertForMaggie] as unknown as Alert[] + const apiResponse = [] as Alert[] when(alertsApiClient.getAlertsForPrisoners) .calledWith(atLeast(expectedOffenderNumbers)) diff --git a/server/services/alertsService.ts b/server/services/alertsService.ts index f3314866a..f4252bf17 100644 --- a/server/services/alertsService.ts +++ b/server/services/alertsService.ts @@ -46,9 +46,7 @@ export default class AlertsService { readonly categoriesWithBadges: Set = new Set(['A', 'E', 'H', 'P']) async getAlertsUsingPrisonerNumbers(prisonerNumbers: string[], user: ServiceUser): Promise { - return this.alertsApiClient - .getAlertsForPrisoners(prisonerNumbers, user) - .then(a => a.content.filter(alert => alert.isActive)) + return this.alertsApiClient.getAlertsForPrisoners(prisonerNumbers, user).then(alerts => alerts?.content) } async getAlertDetails(prisoners: PrisonerDetails[], user: ServiceUser): Promise { From fbcc1e4f596fd58da38c1f0b26a3d948bdddf8d2 Mon Sep 17 00:00:00 2001 From: Natalie Clamp Date: Fri, 10 Jan 2025 11:21:08 +0000 Subject: [PATCH 5/5] SAA-2272: Fix int tests after including query in endpoint call --- integration_tests/e2e/appointments/createAppointment.cy.ts | 2 +- .../e2e/appointments/createAppointmentBackLinks.cy.ts | 2 +- .../e2e/appointments/createAppointmentCheckAnswers.cy.ts | 2 +- .../e2e/appointments/createInCellAppointment.cy.ts | 2 +- .../e2e/appointments/createRepeatAppointment.cy.ts | 2 +- .../e2e/appointments/createRetrospectiveAppointment.cy.ts | 2 +- integration_tests/e2e/appointments/duplicateAppointment.cy.ts | 2 +- integration_tests/e2e/appointments/editAppointments.cy.ts | 4 ++-- .../e2e/appointments/reviewNonAssociationsPage.cy.ts | 2 +- server/data/alertsApiClient.test.ts | 3 ++- 10 files changed, 12 insertions(+), 11 deletions(-) diff --git a/integration_tests/e2e/appointments/createAppointment.cy.ts b/integration_tests/e2e/appointments/createAppointment.cy.ts index ecaf617d8..0ba7005ef 100644 --- a/integration_tests/e2e/appointments/createAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createAppointment.cy.ts @@ -80,7 +80,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', getNonAssociationsBetweenA8644DYA1350DZ) }) diff --git a/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts b/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts index 55473fa1c..a779e9556 100644 --- a/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts +++ b/integration_tests/e2e/appointments/createAppointmentBackLinks.cy.ts @@ -52,7 +52,7 @@ context('Create group appointment - back links', () => { cy.stubEndpoint('POST', '/appointment-series', getAppointmentSeries) cy.stubEndpoint('GET', '/appointment-series/10/details', getAppointmentSeriesDetails) cy.stubEndpoint('GET', '/appointments/11/details', getAppointmentDetails) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsA8644DY) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlertsA8644DY) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts b/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts index e0a0ea233..910560940 100644 --- a/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts +++ b/integration_tests/e2e/appointments/createAppointmentCheckAnswers.cy.ts @@ -66,7 +66,7 @@ context('Create group appointment - check answers change links', () => { cy.stubEndpoint('POST', '/appointment-series', getAppointmentSeries) cy.stubEndpoint('GET', '/appointment-series/10/details', getAppointmentSeriesDetails) cy.stubEndpoint('GET', '/appointments/11/details', getAppointmentDetails) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsA8644DY) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlertsA8644DY) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createInCellAppointment.cy.ts b/integration_tests/e2e/appointments/createInCellAppointment.cy.ts index e1f0ac8fc..342f87336 100644 --- a/integration_tests/e2e/appointments/createInCellAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createInCellAppointment.cy.ts @@ -58,7 +58,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts b/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts index 83ef65698..09a2e260d 100644 --- a/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createRepeatAppointment.cy.ts @@ -86,7 +86,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts b/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts index 1e036033f..2be91d9a0 100644 --- a/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts +++ b/integration_tests/e2e/appointments/createRetrospectiveAppointment.cy.ts @@ -73,7 +73,7 @@ context('Create a retrospective appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', []) }) diff --git a/integration_tests/e2e/appointments/duplicateAppointment.cy.ts b/integration_tests/e2e/appointments/duplicateAppointment.cy.ts index 2686be523..071d952fd 100644 --- a/integration_tests/e2e/appointments/duplicateAppointment.cy.ts +++ b/integration_tests/e2e/appointments/duplicateAppointment.cy.ts @@ -56,7 +56,7 @@ context('Duplicate appointment', () => { JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) cy.stubEndpoint('GET', '/appointment-series/10/details', getGroupAppointmentSeriesDetails) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlerts) cy.stubEndpoint('POST', `/scheduled-events/prison/MDI\\?date=${tomorrowFormatted}`, getScheduledEvents) cy.stubEndpoint('POST', '/appointment-series', getAppointmentSeries) }) diff --git a/integration_tests/e2e/appointments/editAppointments.cy.ts b/integration_tests/e2e/appointments/editAppointments.cy.ts index 50c6e8c7a..1702871c0 100644 --- a/integration_tests/e2e/appointments/editAppointments.cy.ts +++ b/integration_tests/e2e/appointments/editAppointments.cy.ts @@ -61,8 +61,8 @@ context('Edit appointment', () => { cy.stubEndpoint('GET', '/prison/MDI/prisoners\\?term=saunders&size=50', getPrisonPrisonersG6123VU) cy.stubEndpoint('GET', '/prisoner/G0995GW', getPrisonerG0995GW) cy.stubEndpoint('GET', '/prisoner/G6123VU', getPrisonerG6123VU) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsG0995GW) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlertsG0995GWG6123VU) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlertsG0995GW) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlertsG0995GWG6123VU) cy.stubEndpoint('POST', '/non-associations/between', getNonAssociationsBetweenG0995GWA1350DZ) cy.stubEndpoint('POST', '/prisoner-search/prisoner-numbers', getPrisonPrisonersG0995GWA1351DZ) diff --git a/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts b/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts index 113e92db1..ac0feb5c8 100644 --- a/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts +++ b/integration_tests/e2e/appointments/reviewNonAssociationsPage.cy.ts @@ -73,7 +73,7 @@ context('Create group appointment', () => { '/users/jsmith', JSON.parse('{"name": "John Smith", "username": "jsmith", "authSource": "nomis"}'), ) - cy.stubEndpoint('POST', '/search/alerts/prison-numbers', getPrisonerAlerts) + cy.stubEndpoint('POST', '/search/alerts/prison-numbers\\?includeInactive=false', getPrisonerAlerts) cy.stubEndpoint('POST', '/non-associations/between', getNonAssociationsBetweenA8644DYA1350DZ) }) diff --git a/server/data/alertsApiClient.test.ts b/server/data/alertsApiClient.test.ts index 074f72d97..c2ddf530d 100644 --- a/server/data/alertsApiClient.test.ts +++ b/server/data/alertsApiClient.test.ts @@ -29,7 +29,8 @@ describe('alertsApiClient', () => { const response = { data: 'some data' } fakeAlertsApi - .post('/search/alerts/prison-numbers?includeInactive=false') + .post('/search/alerts/prison-numbers') + .query({ includeInactive: false }) .matchHeader('authorization', `Bearer accessToken`) .reply(200, response)