From 33be21628504a40d6a3c6781c3ed02a40a6b7933 Mon Sep 17 00:00:00 2001 From: karlfosterBCL Date: Tue, 4 Mar 2025 07:56:58 +0000 Subject: [PATCH 1/3] initial set --- server/data/breachNoticeApiClient.ts | 1 + server/views/pages/basic-details.njk | 2 + server/views/pages/check-your-report.njk | 2 + server/views/pages/warning-type.njk | 144 +++++++++++----------- server/views/partials/review-required.njk | 11 ++ wiremock/mappings/breach-notice-api.json | 74 +++++++++++ 6 files changed, 163 insertions(+), 71 deletions(-) create mode 100644 server/views/partials/review-required.njk diff --git a/server/data/breachNoticeApiClient.ts b/server/data/breachNoticeApiClient.ts index e2ca0a3..c7fb1dc 100644 --- a/server/data/breachNoticeApiClient.ts +++ b/server/data/breachNoticeApiClient.ts @@ -71,6 +71,7 @@ export interface BreachNotice { nextAppointmentSaved: boolean useDefaultAddress: boolean useDefaultReplyAddress: boolean + reviewRequiredDate: Date breachNoticeContactList: BreachNoticeContact[] breachNoticeRequirementList: BreachNoticeRequirement[] optionalNumberChecked: boolean diff --git a/server/views/pages/basic-details.njk b/server/views/pages/basic-details.njk index 23211f1..53b3c30 100644 --- a/server/views/pages/basic-details.njk +++ b/server/views/pages/basic-details.njk @@ -59,6 +59,8 @@
+ {% include "partials/review-required.njk" %} +

Breach Notice - Basic Details

Title and Full Name

diff --git a/server/views/pages/check-your-report.njk b/server/views/pages/check-your-report.njk index e81d12e..45d423c 100644 --- a/server/views/pages/check-your-report.njk +++ b/server/views/pages/check-your-report.njk @@ -31,6 +31,8 @@
+ {% include "partials/review-required.njk" %} +

Breach Notice - Check Your Answers

diff --git a/server/views/pages/warning-type.njk b/server/views/pages/warning-type.njk index c0dc74e..ba03411 100644 --- a/server/views/pages/warning-type.njk +++ b/server/views/pages/warning-type.njk @@ -5,85 +5,87 @@ {% block content %} - {% from "moj/components/side-navigation/macro.njk" import mojSideNavigation %} - {% from "govuk/components/radios/macro.njk" import govukRadios %} - {% from "govuk/components/select/macro.njk" import govukSelect %} - {% from "govuk/components/button/macro.njk" import govukButton %} - {% from "moj/components/date-picker/macro.njk" import mojDatePicker %} - {% from "govuk/components/table/macro.njk" import govukTable %} - {% from "govuk/components/select/macro.njk" import govukSelect %} + {% from "moj/components/side-navigation/macro.njk" import mojSideNavigation %} + {% from "govuk/components/radios/macro.njk" import govukRadios %} + {% from "govuk/components/select/macro.njk" import govukSelect %} + {% from "govuk/components/button/macro.njk" import govukButton %} + {% from "moj/components/date-picker/macro.njk" import mojDatePicker %} + {% from "govuk/components/table/macro.njk" import govukTable %} + {% from "govuk/components/select/macro.njk" import govukSelect %} -
- {% set errorList =[ - { text: errorMessages.warningType.text, href: "#warning-type" } if errorMessages.warningType else None, - { text: errorMessages.sentenceType.text, href: "#sentence-type" } if errorMessages.sentenceType else None - ] | reject("undefined") %} + + {% set errorList =[ + { text: errorMessages.warningType.text, href: "#warning-type" } if errorMessages.warningType else None, + { text: errorMessages.sentenceType.text, href: "#sentence-type" } if errorMessages.sentenceType else None + ] | reject("undefined") %} - {% if errorMessages | length > 0 %} - {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} - {{ govukErrorSummary({ titleText: "There is a problem", errorList: errorList }) }} - {% endif %} + {% if errorMessages | length > 0 %} + {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} + {{ govukErrorSummary({ titleText: "There is a problem", errorList: errorList }) }} + {% endif %} - -
-
+ +
+
- {% include "pages/side-nav.njk" %} + {% include "pages/side-nav.njk" %} -
+
-
-

Breach Notice - Warning Type

+
+ {% include "partials/review-required.njk" %} - {{ govukRadios({ - id: "warning-type", - name: "warningType", - fieldset: { - legend: { - text: "Select a Warning Type", - isPageHeading: false, - classes: "govuk-fieldset__legend--m" - } - }, - items: warningTypeRadioButtons, - errorMessage: { - text: errorMessages.warningType.text - } if errorMessages.warningType.text - }) }} +

Breach Notice - Warning Type

- {{ govukSelect({ - id: "sentence-type", - name: "sentenceType", - label: { - text: "Select the Sentence Type being enforced", - classes: "govuk-fieldset__legend--m" - }, - items: sentenceTypeSelectItems, - errorMessage: { - text: errorMessages.sentenceType.text - } if errorMessages.sentenceType.text - }) }} + {{ govukRadios({ + id: "warning-type", + name: "warningType", + fieldset: { + legend: { + text: "Select a Warning Type", + isPageHeading: false, + classes: "govuk-fieldset__legend--m" + } + }, + items: warningTypeRadioButtons, + errorMessage: { + text: errorMessages.warningType.text + } if errorMessages.warningType.text + }) }} -
- {{ govukButton({ - text: "Continue", - preventDoubleClick: "true", - type: "submit", - attributes: { - id: "continue-button" - } - }) }} - {{ govukButton({ - text: "Save Progress and Close", - href: "/basic-details/"+breachNotice.id, - classes: "govuk-button--secondary", - preventDoubleClick: "true", - attributes: { - id: "close-button" - } - }) }} -
-
+ {{ govukSelect({ + id: "sentence-type", + name: "sentenceType", + label: { + text: "Select the Sentence Type being enforced", + classes: "govuk-fieldset__legend--m" + }, + items: sentenceTypeSelectItems, + errorMessage: { + text: errorMessages.sentenceType.text + } if errorMessages.sentenceType.text + }) }} + +
+ {{ govukButton({ + text: "Continue", + preventDoubleClick: "true", + type: "submit", + attributes: { + id: "continue-button" + } + }) }} + {{ govukButton({ + text: "Save Progress and Close", + href: "/basic-details/"+breachNotice.id, + classes: "govuk-button--secondary", + preventDoubleClick: "true", + attributes: { + id: "close-button" + } + }) }}
- +
+
+ {% endblock %} diff --git a/server/views/partials/review-required.njk b/server/views/partials/review-required.njk new file mode 100644 index 0000000..0e4aad4 --- /dev/null +++ b/server/views/partials/review-required.njk @@ -0,0 +1,11 @@ +{%- from "moj/components/alert/macro.njk" import mojAlert -%} + +{% if breachNotice.reviewRequiredDate != null %} + {{ mojAlert({ + variant: "warning", + title: "Review required", + showTitleAsHeading: true, + dismissible: false, + html: 'Changes have been made in NDelius. Please review this report and discard if necessary.' + }) }} +{% endif %} diff --git a/wiremock/mappings/breach-notice-api.json b/wiremock/mappings/breach-notice-api.json index f56f6bd..c0bc3d5 100644 --- a/wiremock/mappings/breach-notice-api.json +++ b/wiremock/mappings/breach-notice-api.json @@ -679,6 +679,80 @@ }, "transformers": ["response-template"] } + }, + { + "request": { + "url": "/breach-notice-api/breach-notice/00000000-0000-0000-0000-100000000101", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "id": "00000000-0000-0000-0000-100000000101", + "crn": "X100504", + "titleAndFullName": "Mr Billy The Kid", + "dateOfLetter": "2025-01-01", + "referenceNumber": "REF0001", + "responseRequiredByDate": "2025-01-01", + "breachNoticeTypeCode": "BW", + "breachNoticeTypeDescription": "Breach Warning", + "breachConditionTypeCode": "BRE", + "breachConditionTypeDescription": "Breach", + "breachSentenceTypeCode": "CO", + "breachSentenceTypeDescription": "Community Order(s)", + "responsibleOfficer": "An Officer", + "contactNumber": "0892929187", + "nextAppointmentType": "App Type", + "nextAppointmentDate": "2025-01-01T12:34:56", + "nextAppointmentLocation": "Location", + "nextAppointmentOfficer": "Officer", + "nextAppointmentContact": "0292827177", + "completedDate": null, + "offenderAddress": { + "addressId": 12345, + "type": "Postal", + "buildingName": null, + "buildingNumber": "21", + "streetName": "Postal Street", + "district": "Postal District", + "townCity": "PostCity", + "county": "Postal County", + "postcode": "NE30 3ZZ" + }, + "replyAddress": { + "addressId": 33333, + "type": "Postal", + "buildingName": null, + "buildingNumber": "21", + "streetName": "Reply Street", + "district": "Reply District", + "townCity": "Reply City", + "county": "Reply County", + "postcode": "NE22 3AA" + }, + "basicDetailsSaved": true, + "warningTypeSaved": true, + "warningDetailsSaved": true, + "nextAppointmentSaved": true, + "useDefaultAddress": true, + "useDefaultReplyAddress": true, + "reviewRequiredDate": "2025-01-01T12:34:56", + "breachNoticeContactList": [ + { + "contactId": 1 + } + ], + "breachNoticeRequirementList": [ + { + "requirementId": 1 + } + ] + }, + "headers": { + "Content-Type": "application/json;charset=UTF-8" + }, + "transformers": ["response-template"] + } } ] } From 3ec24a2a6f11a7f5a46d9ee908e4bc69c9e5f80a Mon Sep 17 00:00:00 2001 From: karlfosterBCL Date: Tue, 4 Mar 2025 09:29:11 +0000 Subject: [PATCH 2/3] changed wording of message --- package-lock.json | 16 +++++++--------- package.json | 2 +- server/views/partials/review-required.njk | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00240cd..ab0a901 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@aws-sdk/client-sns": "^3.744.0", "@aws-sdk/client-sqs": "^3.744.0", "@js-joda/core": "^5.6.4", - "@ministryofjustice/frontend": "^3.3.1", + "@ministryofjustice/frontend": "^3.4.0", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", "agentkeepalive": "^4.6.0", "applicationinsights": "^2.9.6", @@ -3066,19 +3066,17 @@ } }, "node_modules/@ministryofjustice/frontend": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@ministryofjustice/frontend/-/frontend-3.3.1.tgz", - "integrity": "sha512-4npwkub8xkhp+YFUK9MIm8armTTs7hzkvT33Ijetxi6aI4Ezzym7XPv4XjQavYAewmv+CHv6WqbBCqtJrWwtmg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@ministryofjustice/frontend/-/frontend-3.7.0.tgz", + "integrity": "sha512-o84Sqy+1jqc1vaxIZfyG4VwJzdpDUK88O9COJFVLcFmW+Np80onlFlGAVjDUxNHYvtBdJx40w1dayq53wmtxgQ==", "license": "MIT", - "dependencies": { - "govuk-frontend": "^5.0.0", - "moment": "^2.27.0" - }, "engines": { "node": ">= 4.2.0" }, "peerDependencies": { - "jquery": "^3.6.0" + "govuk-frontend": "5.x", + "jquery": "3.x", + "moment": "2.x" } }, "node_modules/@ministryofjustice/hmpps-monitoring": { diff --git a/package.json b/package.json index 8ef5557..36cd38c 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@aws-sdk/client-sqs": "^3.744.0", "@aws-sdk/client-sns": "^3.744.0", "@js-joda/core": "^5.6.4", - "@ministryofjustice/frontend": "^3.3.1", + "@ministryofjustice/frontend": "^3.4.0", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", "agentkeepalive": "^4.6.0", "applicationinsights": "^2.9.6", diff --git a/server/views/partials/review-required.njk b/server/views/partials/review-required.njk index 0e4aad4..862a6d6 100644 --- a/server/views/partials/review-required.njk +++ b/server/views/partials/review-required.njk @@ -6,6 +6,6 @@ title: "Review required", showTitleAsHeading: true, dismissible: false, - html: 'Changes have been made in NDelius. Please review this report and discard if necessary.' + html: 'Changes have been made in NDelius. Please review this report and delete if necessary.' }) }} {% endif %} From f6295a197619248a0556d122ebd13db6e145c93b Mon Sep 17 00:00:00 2001 From: karlfosterBCL Date: Fri, 7 Mar 2025 16:35:50 +0000 Subject: [PATCH 3/3] BR-59, BR-65 - Merge and Unmerge events --- README.md | 4 +- helm_deploy/hmpps-breach-notice-ui/Chart.yaml | 4 +- .../hmpps-breach-notice-ui/values.yaml | 16 +- helm_deploy/values-dev.yaml | 9 +- helm_deploy/values-preprod.yaml | 3 +- helm_deploy/values-prod.yaml | 9 +- integration_tests/e2e/domainEvent.cy.ts | 76 ++ integration_tests/e2e/warningDetails.cy.ts | 27 + package-lock.json | 738 +++++------------- package.json | 27 +- server/config.ts | 12 +- server/data/breachNoticeApiClient.ts | 81 +- server/data/hmppsSnsClient.ts | 32 +- server/data/ndeliusIntegrationApiClient.ts | 34 +- server/data/restClient.ts | 9 +- server/data/uiModels.ts | 15 + server/routes/basicDetails.ts | 3 +- server/routes/checkYourReport.ts | 14 +- server/routes/index.ts | 4 +- server/routes/nextAppointment.ts | 106 +-- server/routes/reportDeleted.ts | 40 +- server/routes/warningDetails.ts | 280 ++++++- server/routes/warningType.ts | 74 +- server/utils/dateUtils.ts | 3 +- server/utils/nunjucksSetup.ts | 2 + server/utils/utils.ts | 6 + server/views/pages/check-your-report.njk | 108 +-- server/views/pages/next-appointment.njk | 1 + server/views/pages/warning-details.njk | 117 ++- server/views/partials/review-required.njk | 17 +- wiremock/mappings/breach-notice-api.json | 164 +++- wiremock/mappings/ndelius-integration.json | 56 +- 32 files changed, 1204 insertions(+), 887 deletions(-) create mode 100644 integration_tests/e2e/domainEvent.cy.ts create mode 100644 integration_tests/e2e/warningDetails.cy.ts create mode 100644 server/data/uiModels.ts diff --git a/README.md b/README.md index f06781a..0f76f3d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # hmpps-breach-notice-ui [![repo standards badge](https://img.shields.io/endpoint?labelColor=231f20&color=005ea5&style=flat&label=MoJ%20Compliant&url=https%3A%2F%2Foperations-engineering-reports-prod.cloud-platform.service.justice.gov.uk%2Fapi%2Fv1%2Fcompliant_public_repositories%2Fendpoint%2Fhmpps-breach-notice-ui&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABmJLR0QA/wD/AP+gvaeTAAAHJElEQVRYhe2YeYyW1RWHnzuMCzCIglBQlhSV2gICKlHiUhVBEAsxGqmVxCUUIV1i61YxadEoal1SWttUaKJNWrQUsRRc6tLGNlCXWGyoUkCJ4uCCSCOiwlTm6R/nfPjyMeDY8lfjSSZz3/fee87vnnPu75z3g8/kM2mfqMPVH6mf35t6G/ZgcJ/836Gdug4FjgO67UFn70+FDmjcw9xZaiegWX29lLLmE3QV4Glg8x7WbFfHlFIebS/ANj2oDgX+CXwA9AMubmPNvuqX1SnqKGAT0BFoVE9UL1RH7nSCUjYAL6rntBdg2Q3AgcAo4HDgXeBAoC+wrZQyWS3AWcDSUsomtSswEtgXaAGWlVI2q32BI0spj9XpPww4EVic88vaC7iq5Hz1BvVf6v3qe+rb6ji1p3pWrmtQG9VD1Jn5br+Knmm70T9MfUh9JaPQZu7uLsR9gEsJb3QF9gOagO7AuUTom1LpCcAkoCcwQj0VmJregzaipA4GphNe7w/MBearB7QLYCmlGdiWSm4CfplTHwBDgPHAFmB+Ah8N9AE6EGkxHLhaHU2kRhXc+cByYCqROs05NQq4oR7Lnm5xE9AL+GYC2gZ0Jmjk8VLKO+pE4HvAyYRnOwOH5N7NhMd/WKf3beApYBWwAdgHuCLn+tatbRtgJv1awhtd838LEeq30/A7wN+AwcBt+bwpD9AdOAkYVkpZXtVdSnlc7QI8BlwOXFmZ3oXkdxfidwmPrQXeA+4GuuT08QSdALxC3OYNhBe/TtzON4EziZBXD36o+q082BxgQuqvyYL6wtBY2TyEyJ2DgAXAzcC1+Xxw3RlGqiuJ6vE6QS9VGZ/7H02DDwAvELTyMDAxbfQBvggMAAYR9LR9J2cluH7AmnzuBowFFhLJ/wi7yiJgGXBLPq8A7idy9kPgvAQPcC9wERHSVcDtCfYj4E7gr8BRqWMjcXmeB+4tpbyG2kG9Sl2tPqF2Uick8B+7szyfvDhR3Z7vvq/2yqpynnqNeoY6v7LvevUU9QN1fZ3OTeppWZmeyzRoVu+rhbaHOledmoQ7LRd3SzBVeUo9Wf1DPs9X90/jX8m/e9Rn1Mnqi7nuXXW5+rK6oU7n64mjszovxyvVh9WeDcTVnl5KmQNcCMwvpbQA1xE8VZXhwDXAz4FWIkfnAlcBAwl6+SjD2wTcmPtagZnAEuA3dTp7qyNKKe8DW9UeBCeuBsbsWKVOUPvn+MRKCLeq16lXqLPVFvXb6r25dlaGdUx6cITaJ8fnpo5WI4Wuzcjcqn5Y8eI/1F+n3XvUA1N3v4ZamIEtpZRX1Y6Z/DUK2g84GrgHuDqTehpBCYend94jbnJ34DDgNGArQT9bict3Y3p1ZCnlSoLQb0sbgwjCXpY2blc7llLW1UAMI3o5CD4bmuOlwHaC6xakgZ4Z+ibgSxnOgcAI4uavI27jEII7909dL5VSrimlPKgeQ6TJCZVQjwaOLaW8BfyWbPEa1SaiTH1VfSENd85NDxHt1plA71LKRvX4BDaAKFlTgLeALtliDUqPrSV6SQCBlypgFlbmIIrCDcAl6nPAawmYhlLKFuB6IrkXAadUNj6TXlhDcCNEB/Jn4FcE0f4UWEl0NyWNvZxGTs89z6ZnatIIrCdqcCtRJmcCPwCeSN3N1Iu6T4VaFhm9n+riypouBnepLsk9p6p35fzwvDSX5eVQvaDOzjnqzTl+1KC53+XzLINHd65O6lD1DnWbepPBhQ3q2jQyW+2oDkkAtdt5udpb7W+Q/OFGA7ol1zxu1tc8zNHqXercfDfQIOZm9fR815Cpt5PnVqsr1F51wI9QnzU63xZ1o/rdPPmt6enV6sXqHPVqdXOCe1rtrg5W7zNI+m712Ir+cer4POiqfHeJSVe1Raemwnm7xD3mD1E/Z3wIjcsTdlZnqO8bFeNB9c30zgVG2euYa69QJ+9G90lG+99bfdIoo5PU4w362xHePxl1slMab6tV72KUxDvzlAMT8G0ZohXq39VX1bNzzxij9K1Qb9lhdGe931B/kR6/zCwY9YvuytCsMlj+gbr5SemhqkyuzE8xau4MP865JvWNuj0b1YuqDkgvH2GkURfakly01Cg7Cw0+qyXxkjojq9Lw+vT2AUY+DlF/otYq1Ixc35re2V7R8aTRg2KUv7+ou3x/14PsUBn3NG51S0XpG0Z9PcOPKWSS0SKNUo9Rv2Mmt/G5WpPF6pHGra7Jv410OVsdaz217AbkAPX3ubkm240belCuudT4Rp5p/DyC2lf9mfq1iq5eFe8/lu+K0YrVp0uret4nAkwlB6vzjI/1PxrlrTp/oNHbzTJI92T1qAT+BfW49MhMg6JUp7ehY5a6Tl2jjmVvitF9fxo5Yq8CaAfAkzLMnySt6uz/1k6bPx59CpCNxGfoSKA30IPoH7cQXdArwCOllFX/i53P5P9a/gNkKpsCMFRuFAAAAABJRU5ErkJggg==)](https://operations-engineering-reports-prod.cloud-platform.service.justice.gov.uk/public-report/hmpps-breach-notice-ui) -[![CircleCI](https://circleci.com/gh/ministryofjustice/hmpps-breach-notice-ui/tree/main.svg?style=svg)](https://circleci.com/gh/ministryofjustice/hmpps-breach-notice-ui) +[![Pipeline](https://github.com/ministryofjustice/hmpps-breach-notice-ui/actions/workflows/pipeline.yml/badge.svg)](https://github.com/ministryofjustice/hmpps-breach-notice-ui/actions/workflows/pipeline.yml) ## Get started +Dev: https://breach-notice-dev.hmpps.service.justice.gov.uk + ### Pre-requisites You'll need to install: diff --git a/helm_deploy/hmpps-breach-notice-ui/Chart.yaml b/helm_deploy/hmpps-breach-notice-ui/Chart.yaml index 7a71982..600e4e8 100644 --- a/helm_deploy/hmpps-breach-notice-ui/Chart.yaml +++ b/helm_deploy/hmpps-breach-notice-ui/Chart.yaml @@ -5,8 +5,8 @@ name: hmpps-breach-notice-ui version: 0.2.0 dependencies: - name: generic-service - version: "3.6" + version: "3.9" repository: https://ministryofjustice.github.io/hmpps-helm-charts - name: generic-prometheus-alerts - version: "1.9" + version: "1.11" repository: https://ministryofjustice.github.io/hmpps-helm-charts diff --git a/helm_deploy/hmpps-breach-notice-ui/values.yaml b/helm_deploy/hmpps-breach-notice-ui/values.yaml index e9e5d0b..cf32ce4 100644 --- a/helm_deploy/hmpps-breach-notice-ui/values.yaml +++ b/helm_deploy/hmpps-breach-notice-ui/values.yaml @@ -3,12 +3,12 @@ generic-service: productId: 'HMPPS501' # productId for the product that this belongs too, i.e. DPS001, see README.md for details # the IRSA service account name for accessing AWS resources - # serviceAccountName: "hmpps-breach-notice-ui" + serviceAccountName: hmpps-breach-notice replicaCount: 4 image: - repository: quay.io/hmpps/hmpps-breach-notice-ui + repository: ghcr.io/ministryofjustice/hmpps-breach-notice-ui tag: app_version # override at deployment time port: 3000 @@ -32,6 +32,8 @@ generic-service: REDIS_TLS_ENABLED: 'true' TOKEN_VERIFICATION_ENABLED: 'true' APPLICATIONINSIGHTS_CONNECTION_STRING: 'InstrumentationKey=$(APPINSIGHTS_INSTRUMENTATIONKEY);IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/' + AWS_REGION: 'eu-west-2' + AUDIT_ENABLED: 'true' AUDIT_SQS_REGION: 'eu-west-2' AUDIT_SERVICE_NAME: 'UNASSIGNED' # Your audit service name @@ -51,13 +53,17 @@ generic-service: elasticache-redis: REDIS_HOST: 'primary_endpoint_address' REDIS_AUTH_TOKEN: 'auth_token' - #sqs-hmpps-audit-secret: - # AUDIT_SQS_QUEUE_URL: "sqs_queue_url" - # AUDIT_SQS_QUEUE_NAME: "sqs_queue_name" + hmpps-audit: + AUDIT_SQS_QUEUE_URL: 'sqs_queue_url' + AUDIT_SQS_QUEUE_NAME: 'sqs_queue_name' + hmpps-domain-events: + HMPPS_DOMAIN_EVENTS_TOPIC_ARN: 'topic_arn' allowlist: groups: - internal + - unilink_staff generic-prometheus-alerts: targetApplication: hmpps-breach-notice-ui + alertSeverity: breach-notice-notifications diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index 1898b34..974c0f2 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -11,8 +11,15 @@ generic-service: INGRESS_URL: "https://breach-notice-dev.hmpps.service.justice.gov.uk" HMPPS_AUTH_URL: "https://sign-in-dev.hmpps.service.justice.gov.uk/auth" TOKEN_VERIFICATION_API_URL: "https://token-verification-api-dev.prison.service.justice.gov.uk" + BREACH_NOTICE_API_URL: "https://breach-notice-api-dev.hmpps.service.justice.gov.uk" + NDELIUS_INTEGRATION_URL: "https://breach-notice-and-delius-dev.hmpps.service.justice.gov.uk" ENVIRONMENT_NAME: DEV - AUDIT_ENABLED: "false" + + allowlist: + groups: + - internal + - unilink_staff + - moj_cloud_platform # for end-to-end testing generic-prometheus-alerts: alertSeverity: NON_PROD_ALERTS_SEVERITY_LABEL diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index a2f59ba..995de70 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -11,8 +11,9 @@ generic-service: INGRESS_URL: "https://breach-notice-preprod.hmpps.service.justice.gov.uk" HMPPS_AUTH_URL: "https://sign-in-preprod.hmpps.service.justice.gov.uk/auth" TOKEN_VERIFICATION_API_URL: "https://token-verification-api-preprod.prison.service.justice.gov.uk" + BREACH_NOTICE_API_URL: "https://breach-notice-api-preprod.hmpps.service.justice.gov.uk" + NDELIUS_INTEGRATION_URL: "https://breach-notice-and-delius-preprod.hmpps.service.justice.gov.uk" ENVIRONMENT_NAME: PRE-PRODUCTION - AUDIT_ENABLED: "false" generic-prometheus-alerts: alertSeverity: NON_PROD_ALERTS_SEVERITY_LABEL diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index ddd71a3..ef11226 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -9,7 +9,14 @@ generic-service: INGRESS_URL: "https://breach-notice.hmpps.service.justice.gov.uk" HMPPS_AUTH_URL: "https://sign-in.hmpps.service.justice.gov.uk/auth" TOKEN_VERIFICATION_API_URL: "https://token-verification-api.prison.service.justice.gov.uk" - AUDIT_ENABLED: "false" + BREACH_NOTICE_API_URL: "https://breach-notice-api.hmpps.service.justice.gov.uk" + NDELIUS_INTEGRATION_URL: "https://breach-notice-and-delius.hmpps.service.justice.gov.uk" + + allowlist: + groups: + - internal + - unilink_staff + - probation # for production staff generic-prometheus-alerts: alertSeverity: PROD_ALERTS_SEVERITY_LABEL diff --git a/integration_tests/e2e/domainEvent.cy.ts b/integration_tests/e2e/domainEvent.cy.ts new file mode 100644 index 0000000..e6fab6c --- /dev/null +++ b/integration_tests/e2e/domainEvent.cy.ts @@ -0,0 +1,76 @@ +context('Domain Event checks', () => { + it('Basic Details Merge Alert', () => { + cy.visit('/basic-details/00000000-0000-0000-0000-100000000101') + cy.get('#reviewAlert').should('exist') + }) + + it('Warning Details Merge Alert', () => { + cy.visit('/warning-details/00000000-0000-0000-0000-100000000101') + cy.get('#reviewAlert').should('exist') + }) + + it('Warning Type Merge Alert', () => { + cy.visit('/warning-type/00000000-0000-0000-0000-100000000101') + cy.get('#reviewAlert').should('exist') + }) + + it('Next Appointment Merge Alert', () => { + cy.visit('/next-appointment/00000000-0000-0000-0000-100000000101') + cy.get('#reviewAlert').should('exist') + }) + + it('Check your report Merge Alert', () => { + cy.visit('/check-your-report/00000000-0000-0000-0000-100000000101') + cy.get('#reviewAlert').should('exist') + }) + + it('Basic Details UnMerge Alert', () => { + cy.visit('/basic-details/00000000-0000-0000-0000-100000000102') + cy.get('#reviewAlert').should('exist') + }) + + it('Warning Details UnMerge Alert', () => { + cy.visit('/warning-details/00000000-0000-0000-0000-100000000102') + cy.get('#reviewAlert').should('exist') + }) + + it('Warning Type UnMerge Alert', () => { + cy.visit('/warning-type/00000000-0000-0000-0000-100000000102') + cy.get('#reviewAlert').should('exist') + }) + + it('Next Appointment UnMerge Alert', () => { + cy.visit('/next-appointment/00000000-0000-0000-0000-100000000102') + cy.get('#reviewAlert').should('exist') + }) + + it('Check your report UnMerge Alert', () => { + cy.visit('/check-your-report/00000000-0000-0000-0000-100000000102') + cy.get('#reviewAlert').should('exist') + }) + + it('Basic Details No Alert', () => { + cy.visit('/basic-details/00000000-0000-0000-0000-000000000001') + cy.get('#reviewAlert').should('not.exist') + }) + + it('Warning Details No Alert', () => { + cy.visit('/warning-details/00000000-0000-0000-0000-000000000001') + cy.get('#reviewAlert').should('not.exist') + }) + + it('Warning Type No Alert', () => { + cy.visit('/warning-type/00000000-0000-0000-0000-000000000001') + cy.get('#reviewAlert').should('not.exist') + }) + + it('Next Appointment No Alert', () => { + cy.visit('/next-appointment/00000000-0000-0000-0000-000000000001') + cy.get('#reviewAlert').should('not.exist') + }) + + it('Check your report No Alert', () => { + cy.visit('/check-your-report/00000000-0000-0000-0000-000000000001') + cy.get('#reviewAlert').should('not.exist') + }) +}) diff --git a/integration_tests/e2e/warningDetails.cy.ts b/integration_tests/e2e/warningDetails.cy.ts new file mode 100644 index 0000000..30897f9 --- /dev/null +++ b/integration_tests/e2e/warningDetails.cy.ts @@ -0,0 +1,27 @@ +context('Warning Details page', () => { + it('page will load with no enforceable contacts', () => { + cy.visit('/warning-details/00000000-0000-0000-0000-000000000001') + cy.url().should('include', '/warning-details/00000000-0000-0000-0000-000000000001') + }) + + it('can show readonly value of condition being enforced', () => { + cy.visit('/warning-details/00000000-0000-0000-0000-000000000022') + cy.get('#condition-being-enforced').should('contain.text', 'a condition being enforced') + }) + + it('when a Requirement checkbox is checked, breach reasons dropdown appears', () => { + cy.visit('/warning-details/00000000-0000-0000-0000-000000000022') + cy.get('#failuresBeingEnforcedRequirements').check() + cy.get('#breachreason0').should('exist') + }) + + it('entering a date in an invalid format causes a validation error', () => { + cy.visit('/warning-details/00000000-0000-0000-0000-000000000022') + cy.get('#responseRequiredByDate').type('123456') + cy.get('#continue-button').click() + cy.get('.govuk-error-summary__title').should('exist').should('contain.text', 'There is a problem') + cy.get('#responseRequiredByDate-error') + .should('exist') + .should('contain.text', 'The proposed date for this letter is in an invalid format') + }) +}) diff --git a/package-lock.json b/package-lock.json index ab0a901..f3d8e2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,12 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "@aws-sdk/client-sns": "^3.744.0", - "@aws-sdk/client-sqs": "^3.744.0", + "@aws-sdk/client-sns": "^3.758.0", + "@aws-sdk/client-sqs": "^3.758.0", "@js-joda/core": "^5.6.4", - "@ministryofjustice/frontend": "^3.4.0", + "@ministryofjustice/frontend": "^3.7.0", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", + "@types/qs": "^6.9.18", "agentkeepalive": "^4.6.0", "applicationinsights": "^2.9.6", "body-parser": "^1.20.3", @@ -25,7 +26,7 @@ "csrf-sync": "^4.0.3", "express": "^4.21.2", "express-session": "^1.18.1", - "govuk-frontend": "^5.8.0", + "govuk-frontend": "^5.9.0", "helmet": "^8.0.0", "http-errors": "^2.0.0", "jwt-decode": "^4.0.0", @@ -47,20 +48,20 @@ "@types/express-session": "^1.18.1", "@types/http-errors": "^2.0.4", "@types/jest": "^29.5.14", - "@types/jsonwebtoken": "^9.0.8", - "@types/node": "^22.13.4", + "@types/jsonwebtoken": "^9.0.9", + "@types/node": "^22.13.8", "@types/nunjucks": "^3.2.6", "@types/passport": "^1.0.17", "@types/passport-oauth2": "^1.4.17", "@types/superagent": "^8.1.9", "@types/supertest": "^6.0.2", - "@typescript-eslint/eslint-plugin": "^8.24.0", - "@typescript-eslint/parser": "^8.24.0", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", "audit-ci": "^7.1.0", "aws-sdk-client-mock": "^4.1.0", "chokidar": "^3.6.0", "concurrently": "^9.1.2", - "cypress": "^14.0.3", + "cypress": "^14.1.0", "cypress-multi-reporters": "^2.0.5", "esbuild": "^0.25.0", "esbuild-plugin-clean": "^1.0.1", @@ -76,12 +77,12 @@ "lint-staged": "^15.4.3", "mocha-junit-reporter": "^2.2.1", "nock": "^14.0.1", - "prettier": "^3.5.1", + "prettier": "^3.5.2", "prettier-plugin-jinja-template": "^2.0.0", "supertest": "^7.0.0", - "ts-jest": "^29.2.5", - "typescript": "^5.7.3", - "wiremock": "^3.9.1" + "ts-jest": "^29.2.6", + "typescript": "^5.8.2", + "wiremock": "^3.12.0" }, "engines": { "node": "^22", @@ -228,320 +229,45 @@ } }, "node_modules/@aws-sdk/client-sns": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.750.0.tgz", - "integrity": "sha512-GSZMwCRY4a4+fE6JaYgYTNLB34U5aN96hlrJhISIYPz7ETKYxKXw6inAc718V3H/xJNhNu6XC5iegG6UCeIA1A==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sns/-/client-sns-3.758.0.tgz", + "integrity": "sha512-SyinKtqvp00w1YIfejqm5YsjicBe0GjnlaB5G7n45EPV4vKLfhzdJjVuEuwdRavdzOf99micK77NJbQX5S6TMA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.750.0", - "@aws-sdk/credential-provider-node": "3.750.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/credential-provider-node": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.750.0", + "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.750.0", + "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.4", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.5", - "@smithy/middleware-retry": "^4.0.6", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.5", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.6", - "@smithy/util-defaults-mode-node": "^4.0.6", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/client-sso": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.750.0.tgz", - "integrity": "sha512-y0Rx6pTQXw0E61CaptpZF65qNggjqOgymq/RYZU5vWba5DGQ+iqGt8Yq8s+jfBoBBNXshxq8l8Dl5Uq/JTY1wg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.750.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.750.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.750.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.4", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.5", - "@smithy/middleware-retry": "^4.0.6", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.5", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.6", - "@smithy/util-defaults-mode-node": "^4.0.6", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/core": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.750.0.tgz", - "integrity": "sha512-bZ5K7N5L4+Pa2epbVpUQqd1XLG2uU8BGs/Sd+2nbgTf+lNQJyIxAg/Qsrjz9MzmY8zzQIeRQEkNmR6yVAfCmmQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.4", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.5", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.750.0.tgz", - "integrity": "sha512-In6bsG0p/P31HcH4DBRKBbcDS/3SHvEPjfXV8ODPWZO/l3/p7IRoYBdQ07C9R+VMZU2D0+/Sc/DWK/TUNDk1+Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.750.0.tgz", - "integrity": "sha512-wFB9qqfa20AB0dElsQz5ZlZT5o+a+XzpEpmg0erylmGYqEOvh8NQWfDUVpRmQuGq9VbvW/8cIbxPoNqEbPtuWQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.2", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.5", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.750.0.tgz", - "integrity": "sha512-2YIZmyEr5RUd3uxXpxOLD9G67Bibm4I/65M6vKFP17jVMUT+R1nL7mKqmhEVO2p+BoeV+bwMyJ/jpTYG368PCg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.750.0", - "@aws-sdk/credential-provider-env": "3.750.0", - "@aws-sdk/credential-provider-http": "3.750.0", - "@aws-sdk/credential-provider-process": "3.750.0", - "@aws-sdk/credential-provider-sso": "3.750.0", - "@aws-sdk/credential-provider-web-identity": "3.750.0", - "@aws-sdk/nested-clients": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.750.0.tgz", - "integrity": "sha512-THWHHAceLwsOiowPEmKyhWVDlEUxH07GHSw5AQFDvNQtGKOQl0HSIFO1mKObT2Q2Vqzji9Bq8H58SO5BFtNPRw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.750.0", - "@aws-sdk/credential-provider-http": "3.750.0", - "@aws-sdk/credential-provider-ini": "3.750.0", - "@aws-sdk/credential-provider-process": "3.750.0", - "@aws-sdk/credential-provider-sso": "3.750.0", - "@aws-sdk/credential-provider-web-identity": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.750.0.tgz", - "integrity": "sha512-Q78SCH1n0m7tpu36sJwfrUSxI8l611OyysjQeMiIOliVfZICEoHcLHLcLkiR+tnIpZ3rk7d2EQ6R1jwlXnalMQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.750.0.tgz", - "integrity": "sha512-FGYrDjXN/FOQVi/t8fHSv8zCk+NEvtFnuc4cZUj5OIbM4vrfFc5VaPyn41Uza3iv6Qq9rZg0QOwWnqK8lNrqUw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.750.0", - "@aws-sdk/core": "3.750.0", - "@aws-sdk/token-providers": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.750.0.tgz", - "integrity": "sha512-Nz8zs3YJ+GOTSrq+LyzbbC1Ffpt7pK38gcOyNZv76pP5MswKTUKNYBJehqwa+i7FcFQHsCk3TdhR8MT1ZR23uA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.750.0", - "@aws-sdk/nested-clients": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.750.0.tgz", - "integrity": "sha512-YYcslDsP5+2NZoN3UwuhZGkhAHPSli7HlJHBafBrvjGV/I9f8FuOO1d1ebxGdEP4HyRXUGyh+7Ur4q+Psk0ryw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@smithy/core": "^3.1.4", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/nested-clients": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.750.0.tgz", - "integrity": "sha512-OH68BRF0rt9nDloq4zsfeHI0G21lj11a66qosaljtEP66PWm7tQ06feKbFkXHT5E1K3QhJW3nVyK8v2fEBY5fg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.750.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.750.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.750.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.4", + "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.5", - "@smithy/middleware-retry": "^4.0.6", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.2", + "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.5", + "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.6", - "@smithy/util-defaults-mode-node": "^4.0.6", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", @@ -552,89 +278,48 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/token-providers": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.750.0.tgz", - "integrity": "sha512-X/KzqZw41iWolwNdc8e3RMcNSMR364viHv78u6AefXOO5eRM40c4/LuST1jDzq35/LpnqRhL7/MuixOetw+sFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/nested-clients": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-sns/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.750.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.750.0.tgz", - "integrity": "sha512-84HJj9G9zbrHX2opLk9eHfDceB+UIHVrmflMzWHpsmo9fDuro/flIBqaVDlE021Osj6qIM0SJJcnL6s23j7JEw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.750.0", - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, "node_modules/@aws-sdk/client-sqs": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.744.0.tgz", - "integrity": "sha512-xLYe8m+gw7J5lW3clXUpX1GgB7ysdYw1lh44B195sUJCuLKI1v170zY21wZKBPYPdkZto8z9nGEZA+VIiMvb0Q==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.758.0.tgz", + "integrity": "sha512-AJ+FxzCkzHuS9ewoPi820dMsoPzq5wj8UvTvDaxwUfIM1LiWAhpSvr+mF7MuplIc6liU6hCndCqGO7lxLVxvrQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.744.0", - "@aws-sdk/credential-provider-node": "3.744.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/credential-provider-node": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-sdk-sqs": "3.744.0", - "@aws-sdk/middleware-user-agent": "3.744.0", + "@aws-sdk/middleware-sdk-sqs": "3.758.0", + "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.744.0", + "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.2", + "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/md5-js": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.3", - "@smithy/middleware-retry": "^4.0.4", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.2", + "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.3", + "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.4", - "@smithy/util-defaults-mode-node": "^4.0.4", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", @@ -646,44 +331,44 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.744.0.tgz", - "integrity": "sha512-mzJxPQ9mcnNY50pi7+pxB34/Dt7PUn0OgkashHdJPTnavoriLWvPcaQCG1NEVAtyzxNdowhpi4KjC+aN1EwAeA==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.758.0.tgz", + "integrity": "sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.744.0", + "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.744.0", + "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.744.0", + "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.2", + "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.3", - "@smithy/middleware-retry": "^4.0.4", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.2", + "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.3", + "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.4", - "@smithy/util-defaults-mode-node": "^4.0.4", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", @@ -695,18 +380,18 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.744.0.tgz", - "integrity": "sha512-R0XLfDDq7MAXYyDf7tPb+m0R7gmzTRRDtPNQ5jvuq8dbkefph5gFMkxZ2zSx7dfTsfYHhBPuTBsQ0c5Xjal3Vg==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.758.0.tgz", + "integrity": "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.2", + "@smithy/core": "^3.1.5", "@smithy/node-config-provider": "^4.0.1", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.3", + "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-middleware": "^4.0.1", "fast-xml-parser": "4.4.1", @@ -717,12 +402,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.744.0.tgz", - "integrity": "sha512-hyjC7xqzAeERorYYjhQG1ivcr1XlxgfBpa+r4pG29toFG60mACyVzaR7+og3kgzjRFAB7D1imMxPQyEvQ1QokA==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.758.0.tgz", + "integrity": "sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.744.0", + "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/types": "^4.1.0", @@ -733,20 +418,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.744.0.tgz", - "integrity": "sha512-k+P1Tl5ewBvVByR6hB726qFIzANgQVf2cY87hZ/e09pQYlH4bfBcyY16VJhkqYnKmv6HMdWxKHX7D8nwlc8Obg==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.758.0.tgz", + "integrity": "sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.744.0", + "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.2", + "@smithy/node-http-handler": "^4.0.3", "@smithy/property-provider": "^4.0.1", "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.3", + "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.0.2", + "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" }, "engines": { @@ -754,18 +439,18 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.744.0.tgz", - "integrity": "sha512-hjEWgkF86tkvg8PIsDiB3KkTj7z8ZFGR0v0OLQYD47o17q1qfoMzZmg9wae3wXp9KzU+lZETo+8oMqX9a+7aVQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.744.0", - "@aws-sdk/credential-provider-env": "3.744.0", - "@aws-sdk/credential-provider-http": "3.744.0", - "@aws-sdk/credential-provider-process": "3.744.0", - "@aws-sdk/credential-provider-sso": "3.744.0", - "@aws-sdk/credential-provider-web-identity": "3.744.0", - "@aws-sdk/nested-clients": "3.744.0", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.758.0.tgz", + "integrity": "sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.758.0", + "@aws-sdk/credential-provider-env": "3.758.0", + "@aws-sdk/credential-provider-http": "3.758.0", + "@aws-sdk/credential-provider-process": "3.758.0", + "@aws-sdk/credential-provider-sso": "3.758.0", + "@aws-sdk/credential-provider-web-identity": "3.758.0", + "@aws-sdk/nested-clients": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", @@ -778,17 +463,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.744.0.tgz", - "integrity": "sha512-4oUfRd6pe/VGmKoav17pPoOO0WP0L6YXmHqtJHSDmFUOAa+Vh0ZRljTj/yBdleRgdO6rOfdWqoGLFSFiAZDrsQ==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.758.0.tgz", + "integrity": "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.744.0", - "@aws-sdk/credential-provider-http": "3.744.0", - "@aws-sdk/credential-provider-ini": "3.744.0", - "@aws-sdk/credential-provider-process": "3.744.0", - "@aws-sdk/credential-provider-sso": "3.744.0", - "@aws-sdk/credential-provider-web-identity": "3.744.0", + "@aws-sdk/credential-provider-env": "3.758.0", + "@aws-sdk/credential-provider-http": "3.758.0", + "@aws-sdk/credential-provider-ini": "3.758.0", + "@aws-sdk/credential-provider-process": "3.758.0", + "@aws-sdk/credential-provider-sso": "3.758.0", + "@aws-sdk/credential-provider-web-identity": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/credential-provider-imds": "^4.0.1", "@smithy/property-provider": "^4.0.1", @@ -801,12 +486,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.744.0.tgz", - "integrity": "sha512-m0d/pDBIaiEAAxWXt/c79RHsKkUkyPOvF2SAMRddVhhOt1GFZI4ml+3f4drmAZfXldIyJmvJTJJqWluVPwTIqQ==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.758.0.tgz", + "integrity": "sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.744.0", + "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", @@ -818,14 +503,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.744.0.tgz", - "integrity": "sha512-xdMufTZOvpbDoDPI2XLu0/Rg3qJ/txpS8IJR63NsCGotHJZ/ucLNKwTcGS40hllZB8qSHTlvmlOzElDahTtx/A==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.758.0.tgz", + "integrity": "sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.744.0", - "@aws-sdk/core": "3.744.0", - "@aws-sdk/token-providers": "3.744.0", + "@aws-sdk/client-sso": "3.758.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/token-providers": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", @@ -837,13 +522,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.744.0.tgz", - "integrity": "sha512-cNk93GZxORzqEojWfXdrPBF6a7Nu3LpPCWG5mV+lH2tbuGsmw6XhKkwpt7o+OiIP4tKCpHlvqOD8f1nmhe1KDA==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.758.0.tgz", + "integrity": "sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.744.0", - "@aws-sdk/nested-clients": "3.744.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/nested-clients": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/types": "^4.1.0", @@ -898,13 +583,13 @@ } }, "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.744.0.tgz", - "integrity": "sha512-6P3VQJat6ocaGWgAtGRlPt0FMeM3TeyfQYx0PF7xIyiRlj12Z3sZZR3539ZoZH53zpD/4JzY04jWGFN83bTZag==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.758.0.tgz", + "integrity": "sha512-jBn6EUimaObuZmx5pOFlLxWQGFnfzerKtQRDGl2htBwI8ncYFfexeF9g9Sx4Np3y5iu9F4RUuUU8+KEE2cqeKA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.734.0", - "@smithy/smithy-client": "^4.1.3", + "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", @@ -915,15 +600,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.744.0.tgz", - "integrity": "sha512-ROUbDQHfVWiBHXd4m9E9mKj1Azby8XCs8RC8OCf9GVH339GSE6aMrPJSzMlsV1LmzPdPIypgp5qqh5NfSrKztg==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.758.0.tgz", + "integrity": "sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.744.0", + "@aws-sdk/core": "3.758.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", - "@smithy/core": "^3.1.2", + "@smithy/core": "^3.1.5", "@smithy/protocol-http": "^5.0.1", "@smithy/types": "^4.1.0", "tslib": "^2.6.2" @@ -933,44 +618,44 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.744.0.tgz", - "integrity": "sha512-Mnrlh4lRY1gZQnKvN2Lh/5WXcGkzC41NM93mtn2uaqOh+DZLCXCttNCfbUesUvYJLOo3lYaOpiDsjTkPVB1yjw==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.758.0.tgz", + "integrity": "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.744.0", + "@aws-sdk/core": "3.758.0", "@aws-sdk/middleware-host-header": "3.734.0", "@aws-sdk/middleware-logger": "3.734.0", "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.744.0", + "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/region-config-resolver": "3.734.0", "@aws-sdk/types": "3.734.0", "@aws-sdk/util-endpoints": "3.743.0", "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.744.0", + "@aws-sdk/util-user-agent-node": "3.758.0", "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.2", + "@smithy/core": "^3.1.5", "@smithy/fetch-http-handler": "^5.0.1", "@smithy/hash-node": "^4.0.1", "@smithy/invalid-dependency": "^4.0.1", "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.3", - "@smithy/middleware-retry": "^4.0.4", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", "@smithy/middleware-serde": "^4.0.2", "@smithy/middleware-stack": "^4.0.1", "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.2", + "@smithy/node-http-handler": "^4.0.3", "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.3", + "@smithy/smithy-client": "^4.1.6", "@smithy/types": "^4.1.0", "@smithy/url-parser": "^4.0.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.4", - "@smithy/util-defaults-mode-node": "^4.0.4", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", "@smithy/util-endpoints": "^3.0.1", "@smithy/util-middleware": "^4.0.1", "@smithy/util-retry": "^4.0.1", @@ -999,12 +684,12 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.744.0.tgz", - "integrity": "sha512-v/1+lWkDCd60Ei6oyhJqli6mTsPEVepLoSMB50vHUVlJP0fzXu/3FMje90/RzeUoh/VugZQJCEv/NNpuC6wztg==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.758.0.tgz", + "integrity": "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "3.744.0", + "@aws-sdk/nested-clients": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/property-provider": "^4.0.1", "@smithy/shared-ini-file-loader": "^4.0.1", @@ -1068,12 +753,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.744.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.744.0.tgz", - "integrity": "sha512-BJURjwIXhNa4heXkLC0+GcL+8wVXaU7JoyW6ckdvp93LL+sVHeR1d5FxXZHQW/pMI4E3gNlKyBqjKaT75tObNQ==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.758.0.tgz", + "integrity": "sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.744.0", + "@aws-sdk/middleware-user-agent": "3.758.0", "@aws-sdk/types": "3.734.0", "@smithy/node-config-provider": "^4.0.1", "@smithy/types": "^4.1.0", @@ -4593,9 +4278,9 @@ "license": "MIT" }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.8.tgz", - "integrity": "sha512-7fx54m60nLFUVYlxAB1xpe9CBWX2vSrk50Y6ogRJ1v5xxtba7qXTg5BgYDN5dq+yuQQ9HaVlHJyAAt1/mxryFg==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4625,9 +4310,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "version": "22.13.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz", + "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==", "dev": true, "license": "MIT", "dependencies": { @@ -4677,7 +4362,6 @@ "version": "6.9.18", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { @@ -4800,17 +4484,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz", - "integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/type-utils": "8.24.0", - "@typescript-eslint/utils": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/type-utils": "8.26.0", + "@typescript-eslint/utils": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -4826,20 +4510,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz", - "integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4" }, "engines": { @@ -4851,18 +4535,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz", - "integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0" + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4873,14 +4557,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz", - "integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.24.0", - "@typescript-eslint/utils": "8.24.0", + "@typescript-eslint/typescript-estree": "8.26.0", + "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -4893,13 +4577,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz", - "integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", "dev": true, "license": "MIT", "engines": { @@ -4911,14 +4595,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz", - "integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/visitor-keys": "8.24.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4934,7 +4618,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -4964,16 +4648,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz", - "integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.24.0", - "@typescript-eslint/types": "8.24.0", - "@typescript-eslint/typescript-estree": "8.24.0" + "@typescript-eslint/scope-manager": "8.26.0", + "@typescript-eslint/types": "8.26.0", + "@typescript-eslint/typescript-estree": "8.26.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4984,17 +4668,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz", - "integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==", + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.24.0", + "@typescript-eslint/types": "8.26.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -6657,9 +6341,9 @@ } }, "node_modules/cypress": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.0.3.tgz", - "integrity": "sha512-yIdvobANw3kS+KF/t5vwjjPNufBA8ux7iQHaWxPTkUw2yCKI72m9mKM24eOwE84Wk4ALPsSvEcGbDrwgmhr4RA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.1.0.tgz", + "integrity": "sha512-pPPj8Uu9NwjaaiXAEcjYZZmgsq6v9Zs1Nw6a+zRF+ANgYSNhH4S32SjFRsvMcuOHR/8dp4GBJhBPqIPSs+TxaA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6675,7 +6359,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", - "ci-info": "^4.0.0", + "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -8970,9 +8654,9 @@ } }, "node_modules/govuk-frontend": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/govuk-frontend/-/govuk-frontend-5.8.0.tgz", - "integrity": "sha512-6l3f/YhDUCWjpmSW3CL95Hg8B+ZLzTf2WYo25ZtCs2Lb8UIzxxxFI8LxG7Ey/z04UuPhUunqFhTwSkQyJ69XbQ==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/govuk-frontend/-/govuk-frontend-5.9.0.tgz", + "integrity": "sha512-8NzmyoDtRFYyHs413DfNPR8Zo6qw6Q02Mruxml/Yfy+EueaOI/JZ4gVM8d0pqzJmTiMcJuHhvxqYEgBRmqeoyA==", "license": "MIT", "engines": { "node": ">= 4.2.0" @@ -13036,9 +12720,9 @@ } }, "node_modules/prettier": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", - "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { @@ -15165,9 +14849,9 @@ } }, "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "version": "29.2.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", + "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", "dev": true, "license": "MIT", "dependencies": { @@ -15178,7 +14862,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.6.3", + "semver": "^7.7.1", "yargs-parser": "^21.1.1" }, "bin": { @@ -15403,9 +15087,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -15721,9 +15405,9 @@ } }, "node_modules/wiremock": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/wiremock/-/wiremock-3.12.0.tgz", - "integrity": "sha512-/s1h6/tfNQPO41PhwS5kRhJQp6XlUSBSf3mCHtyOjJlT+/vOxfO5onKjAQzthslhMQqlHZA/2rc8w4ghEO6g6A==", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/wiremock/-/wiremock-3.12.1.tgz", + "integrity": "sha512-gEJOzFuQTh4emP/bfHPAhL9qLv6LBVn72/jSQKPzNesbp0TT+a/Gx3cSzm0Gc1AGX+WVZ5zFKKemZYMFpqp0mQ==", "dev": true, "license": "Apache 2", "dependencies": { diff --git a/package.json b/package.json index 36cd38c..a6b072a 100644 --- a/package.json +++ b/package.json @@ -78,11 +78,12 @@ ] }, "dependencies": { - "@aws-sdk/client-sqs": "^3.744.0", - "@aws-sdk/client-sns": "^3.744.0", + "@aws-sdk/client-sns": "^3.758.0", + "@aws-sdk/client-sqs": "^3.758.0", "@js-joda/core": "^5.6.4", - "@ministryofjustice/frontend": "^3.4.0", + "@ministryofjustice/frontend": "^3.7.0", "@ministryofjustice/hmpps-monitoring": "^0.0.1-beta.2", + "@types/qs": "^6.9.18", "agentkeepalive": "^4.6.0", "applicationinsights": "^2.9.6", "body-parser": "^1.20.3", @@ -94,7 +95,7 @@ "csrf-sync": "^4.0.3", "express": "^4.21.2", "express-session": "^1.18.1", - "govuk-frontend": "^5.8.0", + "govuk-frontend": "^5.9.0", "helmet": "^8.0.0", "http-errors": "^2.0.0", "jwt-decode": "^4.0.0", @@ -116,20 +117,20 @@ "@types/express-session": "^1.18.1", "@types/http-errors": "^2.0.4", "@types/jest": "^29.5.14", - "@types/jsonwebtoken": "^9.0.8", - "@types/node": "^22.13.4", + "@types/jsonwebtoken": "^9.0.9", + "@types/node": "^22.13.8", "@types/nunjucks": "^3.2.6", "@types/passport": "^1.0.17", "@types/passport-oauth2": "^1.4.17", "@types/superagent": "^8.1.9", "@types/supertest": "^6.0.2", - "@typescript-eslint/eslint-plugin": "^8.24.0", - "@typescript-eslint/parser": "^8.24.0", + "@typescript-eslint/eslint-plugin": "^8.25.0", + "@typescript-eslint/parser": "^8.25.0", "audit-ci": "^7.1.0", "aws-sdk-client-mock": "^4.1.0", "chokidar": "^3.6.0", "concurrently": "^9.1.2", - "cypress": "^14.0.3", + "cypress": "^14.1.0", "cypress-multi-reporters": "^2.0.5", "esbuild": "^0.25.0", "esbuild-plugin-clean": "^1.0.1", @@ -145,11 +146,11 @@ "lint-staged": "^15.4.3", "mocha-junit-reporter": "^2.2.1", "nock": "^14.0.1", - "prettier": "^3.5.1", + "prettier": "^3.5.2", "prettier-plugin-jinja-template": "^2.0.0", "supertest": "^7.0.0", - "ts-jest": "^29.2.5", - "typescript": "^5.7.3", - "wiremock": "^3.9.1" + "ts-jest": "^29.2.6", + "typescript": "^5.8.2", + "wiremock": "^3.12.0" } } diff --git a/server/config.ts b/server/config.ts index ab18848..6828cca 100755 --- a/server/config.ts +++ b/server/config.ts @@ -52,12 +52,12 @@ const snsConfig = () => { const snsEnabled = get('SNS_ENABLED', 'false') === 'true' return { enabled: snsEnabled, - topicArn: get('BREACH_NOTICE_PUBLISH_SNS_TOPIC_ARN', '', snsEnabled && requiredInProduction), - serviceName: get('BREACH_NOTICE_PUBLISH_SERVICE_NAME', 'UNASSIGNED', snsEnabled && requiredInProduction), - region: get('BREACH_NOTICE_PUBLISH_SQS_REGION', 'eu-west-2'), - snsHost: get('BREACH_NOTICE_PUBLISH_SNS_HOST', ''), - key: get('BREACH_NOTICE_PUBLISH_SNS_KEY', ''), - secret: get('BREACH_NOTICE_PUBLISH_SNS_SECRET', ''), + topicArn: get( + 'HMPPS_DOMAIN_EVENTS_TOPIC_ARN', + 'arn:aws:sns:we-west-2:000000000000:domainevents', + snsEnabled && requiredInProduction, + ), + endpoint: process.env.HMPPS_DOMAIN_EVENTS_ENDPOINT, } } diff --git a/server/data/breachNoticeApiClient.ts b/server/data/breachNoticeApiClient.ts index c7fb1dc..9e43ee2 100644 --- a/server/data/breachNoticeApiClient.ts +++ b/server/data/breachNoticeApiClient.ts @@ -1,6 +1,5 @@ import config from '../config' import RestClient from './restClient' -import { ReferenceData } from './ndeliusIntegrationApiClient' import { Address } from './commonModels' export default class BreachNoticeApiClient extends RestClient { @@ -48,7 +47,7 @@ export interface BreachNotice { titleAndFullName: string dateOfLetter: string referenceNumber: string - responseRequiredByDate: string + responseRequiredDate: string breachNoticeTypeCode: string breachNoticeTypeDescription: string breachConditionTypeCode: string @@ -72,10 +71,12 @@ export interface BreachNotice { useDefaultAddress: boolean useDefaultReplyAddress: boolean reviewRequiredDate: Date + reviewEvent: string breachNoticeContactList: BreachNoticeContact[] breachNoticeRequirementList: BreachNoticeRequirement[] optionalNumberChecked: boolean optionalNumber: string + conditionBeingEnforced: string } export interface BreachNoticeContact { @@ -93,78 +94,18 @@ export interface BreachNoticeRequirement { id: string breachNoticeId: string requirementId: number - mainCategoryDescription: string - subCategoryDescription: string + requirementTypeMainCategoryDescription: string + requirementTypeSubCategoryDescription: string rejectionReason: string -} - -// these must be value, text, boolean so they can be fed into MOJ components -export interface SelectItem { - value: string - text: string - selected: boolean -} - -export interface RadioButton { - value: string - text: string - checked: boolean -} - -export interface EnforceableContactRadioButton { - datetime: string - type: ReferenceData - outcome: ReferenceData - notes: string - requirement: Requirement - checked: string - value: string - text: string -} - -export interface Requirement { - id: number - type: ReferenceData - subType: ReferenceData + fromDate: string + toDate: string } export interface WarningDetailsRequirementSelectItem { value: string text: string - selected: boolean - requirements: SelectItem[] -} - -export interface ErrorMessages { - [key: string]: { text: string } -} - -export interface FutureAppointment { - contactId: number - datetime: string - description: string - type: ReferenceData - location: Address - officer: Officer -} - -export interface NextAppointmentDetails { - responsibleOfficer: ResponsibleOfficer - futureAppointments: Array -} - -export interface ResponsibleOfficer { - telephoneNumber: string - name: Name -} - -export interface Officer { - code: string - name: Name -} - -export interface Name { - forename: string - middleName: string - surname: string + checked: boolean + conditional: { + html: string + } } diff --git a/server/data/hmppsSnsClient.ts b/server/data/hmppsSnsClient.ts index 9f5acaf..0f12b0a 100644 --- a/server/data/hmppsSnsClient.ts +++ b/server/data/hmppsSnsClient.ts @@ -2,9 +2,9 @@ import { SNSClient, PublishCommand } from '@aws-sdk/client-sns' import logger from '../../logger' export interface HmppsDomainEvent { - eventType: 'probation-case.breach-notice.created' + eventType: string // The domain event type (eg 'probation-case.breach-notice.created') version: 1 - description: 'A breach notice has been completed for a person on probation' + description: string // Domain event description detailUrl: string // This should be set to the PDF URL for the breach notice occurredAt: string // Current timestamp in ISO format additionalInformation: { @@ -22,35 +22,25 @@ export interface HmppsDomainEvent { export interface SnsClientConfig { topicArn: string - region: string - serviceName: string enabled: boolean - key: string - secret: string - snsHost: string + endpoint: string } export default class HmppsSnsClient { - private snsClient: SNSClient + private readonly snsClient: SNSClient - private topicArn: string + private readonly topicArn: string - private serviceName: string + private readonly enabled: boolean - private enabled: boolean - - constructor(config: SnsClientConfig) { - this.enabled = config.enabled - this.topicArn = config.topicArn - this.serviceName = config.serviceName - this.snsClient = new SNSClient({ - region: config.region, - endpoint: config.snsHost, - credentials: { accessKeyId: config.key, secretAccessKey: config.secret }, - }) + constructor({ enabled, endpoint, topicArn }: SnsClientConfig) { + this.enabled = enabled + this.topicArn = topicArn + this.snsClient = endpoint ? new SNSClient({ endpoint }) : new SNSClient() } async sendMessage(event: HmppsDomainEvent, throwOnError: boolean = true) { + logger.debug('Sending SNS notification', event) if (this.enabled) { try { const publishParams = { diff --git a/server/data/ndeliusIntegrationApiClient.ts b/server/data/ndeliusIntegrationApiClient.ts index 239d7ca..7d5ed31 100644 --- a/server/data/ndeliusIntegrationApiClient.ts +++ b/server/data/ndeliusIntegrationApiClient.ts @@ -26,6 +26,12 @@ export default class NdeliusIntegrationApiClient extends RestClient { }) } + async getNextAppointmentDetails(crn: string): Promise { + return this.get({ + path: `/next-appointment-details/${crn}`, + }) + } + async getLimitedAccessCheck(crn: string, username: string): Promise { return this.get({ path: `/users/${username}/access/${crn}`, @@ -73,7 +79,7 @@ export interface WarningTypeWrapper { export interface WarningDetails { breachReasons: ReferenceData[] - enforceableContactList: EnforceableContact[] + enforceableContacts: EnforceableContact[] } export interface ReferenceData { @@ -114,3 +120,29 @@ export interface Requirement { type: ReferenceData subType: ReferenceData } + +export interface FutureAppointment { + contactId: number + datetime: string + description: string + type: ReferenceData + location: Address + officer: Officer +} + +export interface NextAppointmentDetails { + responsibleOfficer: ResponsibleOfficer + futureAppointments: Array +} + +export interface ResponsibleOfficer { + telephoneNumber: string + name: Name +} + +export interface Officer { + code: string + name: Name +} + +export type EnforceableContactList = Array diff --git a/server/data/restClient.ts b/server/data/restClient.ts index d25e75c..b6524f6 100644 --- a/server/data/restClient.ts +++ b/server/data/restClient.ts @@ -2,11 +2,10 @@ import { Readable } from 'stream' import Agent, { HttpsAgent } from 'agentkeepalive' import superagent from 'superagent' - import logger from '../../logger' +import type { UnsanitisedError } from '../sanitisedError' import sanitiseError from '../sanitisedError' import type { ApiConfig } from '../config' -import type { UnsanitisedError } from '../sanitisedError' interface Request { path: string @@ -68,7 +67,7 @@ export default class RestClient { .responseType(responseType) .timeout(this.timeoutConfig()) - return raw ? result : result.body + return raw ? (result as Response) : result.body } catch (error) { const sanitisedError = sanitiseError(error) logger.warn({ ...sanitisedError }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`) @@ -98,7 +97,7 @@ export default class RestClient { .responseType(responseType) .timeout(this.timeoutConfig()) - return raw ? result : result.body + return raw ? (result as Response) : result.body } catch (error) { const sanitisedError = sanitiseError(error) logger.warn({ ...sanitisedError }, `Error calling ${this.name}, path: '${path}', verb: '${method.toUpperCase()}'`) @@ -140,7 +139,7 @@ export default class RestClient { .responseType(responseType) .timeout(this.timeoutConfig()) - return raw ? result : result.body + return raw ? (result as Response) : result.body } catch (error) { const sanitisedError = sanitiseError(error) logger.warn({ ...sanitisedError }, `Error calling ${this.name}, path: '${path}', verb: 'DELETE'`) diff --git a/server/data/uiModels.ts b/server/data/uiModels.ts new file mode 100644 index 0000000..750f27f --- /dev/null +++ b/server/data/uiModels.ts @@ -0,0 +1,15 @@ +export interface SelectItem { + value: string + text: string + selected: boolean +} + +export interface RadioButton { + value: string + text: string + checked: boolean +} + +export interface ErrorMessages { + [key: string]: { text: string } +} diff --git a/server/routes/basicDetails.ts b/server/routes/basicDetails.ts index 3acdab5..1f2adf7 100644 --- a/server/routes/basicDetails.ts +++ b/server/routes/basicDetails.ts @@ -5,10 +5,11 @@ import { fromUserDate, toUserDate } from '../utils/dateUtils' import { HmppsAuthClient } from '../data' import CommonUtils from '../services/commonUtils' import { combineName } from '../utils/utils' -import BreachNoticeApiClient, { BreachNotice, ErrorMessages, SelectItem } from '../data/breachNoticeApiClient' +import BreachNoticeApiClient, { BreachNotice } from '../data/breachNoticeApiClient' import NdeliusIntegrationApiClient, { BasicDetails } from '../data/ndeliusIntegrationApiClient' import { Address } from '../data/commonModels' import asyncMiddleware from '../middleware/asyncMiddleware' +import { ErrorMessages, SelectItem } from '../data/uiModels' export default function basicDetailsRoutes( router: Router, diff --git a/server/routes/checkYourReport.ts b/server/routes/checkYourReport.ts index 2b1258a..8148a7e 100644 --- a/server/routes/checkYourReport.ts +++ b/server/routes/checkYourReport.ts @@ -1,6 +1,6 @@ import { type RequestHandler, Response, Router } from 'express' import AuditService, { Page } from '../services/auditService' -import BreachNoticeApiClient, { BreachNotice, ErrorMessages } from '../data/breachNoticeApiClient' +import BreachNoticeApiClient, { BreachNotice } from '../data/breachNoticeApiClient' import { HmppsAuthClient } from '../data' import SnsService from '../services/snsService' import CommonUtils from '../services/commonUtils' @@ -8,6 +8,7 @@ import { HmppsDomainEvent } from '../data/hmppsSnsClient' import config from '../config' import { toUserDate, toUserDateFromDateTime, toUserTimeFromDateTime } from '../utils/dateUtils' import asyncMiddleware from '../middleware/asyncMiddleware' +import { ErrorMessages } from '../data/uiModels' export default function checkYourReportRoutes( router: Router, @@ -35,7 +36,7 @@ export default function checkYourReportRoutes( }) const basicDetailsDateOfLetter: string = toUserDate(breachNotice.dateOfLetter) - const responseRequiredByDate: string = toUserDate(breachNotice.responseRequiredByDate) + const responseRequiredByDate: string = toUserDate(breachNotice.responseRequiredDate) const nextAppointmentDate: string = toUserDateFromDateTime(breachNotice.nextAppointmentDate) const nextAppointmentTime: string = toUserTimeFromDateTime(breachNotice.nextAppointmentDate) @@ -112,7 +113,7 @@ export default function checkYourReportRoutes( }) function validateCheckYourReport(breachNotice: BreachNotice): boolean { - if ( + return ( breachNotice.crn != null && breachNotice.titleAndFullName != null && breachNotice.offenderAddress != null && @@ -124,16 +125,13 @@ export default function checkYourReportRoutes( breachNotice.breachNoticeContactList.length > 0 && breachNotice.breachNoticeRequirementList != null && breachNotice.breachNoticeRequirementList.length > 0 && - breachNotice.responseRequiredByDate != null && + breachNotice.responseRequiredDate != null && breachNotice.nextAppointmentType != null && breachNotice.nextAppointmentDate != null && breachNotice.nextAppointmentLocation != null && breachNotice.nextAppointmentOfficer != null && breachNotice.responsibleOfficer != null - ) { - return true - } - return false + ) } return router diff --git a/server/routes/index.ts b/server/routes/index.ts index cad40dd..f7cf577 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -29,10 +29,10 @@ export default function routes({ auditService, hmppsAuthClient, snsService, comm basicDetailsRoutes(router, auditService, hmppsAuthClient, commonUtils) warningTypeRoutes(router, auditService, hmppsAuthClient, commonUtils) - warningDetailsRoutes(router, auditService) + warningDetailsRoutes(router, auditService, hmppsAuthClient, commonUtils) checkYourReportRoutes(router, auditService, hmppsAuthClient, snsService, commonUtils) pdfMaintenanceRoutes(router, auditService, hmppsAuthClient, commonUtils) - reportDeletedRoutes(router, auditService) + reportDeletedRoutes(router, auditService, hmppsAuthClient, snsService) reportCompletedRoutes(router, auditService, hmppsAuthClient) nextAppointmentRoutes(router, auditService, hmppsAuthClient, commonUtils) return router diff --git a/server/routes/nextAppointment.ts b/server/routes/nextAppointment.ts index 7640e88..e1be4f4 100644 --- a/server/routes/nextAppointment.ts +++ b/server/routes/nextAppointment.ts @@ -1,20 +1,12 @@ import { type RequestHandler, Router } from 'express' -import { LocalDateTime } from '@js-joda/core' import AuditService, { Page } from '../services/auditService' -import BreachNoticeApiClient, { - BreachNotice, - ErrorMessages, - FutureAppointment, - Name, - NextAppointmentDetails, - Officer, - RadioButton, -} from '../data/breachNoticeApiClient' +import BreachNoticeApiClient, { BreachNotice } from '../data/breachNoticeApiClient' import { HmppsAuthClient } from '../data' import CommonUtils from '../services/commonUtils' -import { Address } from '../data/commonModels' import { toUserDate, toUserTime } from '../utils/dateUtils' import asyncMiddleware from '../middleware/asyncMiddleware' +import NdeliusIntegrationApiClient, { FutureAppointment, Name } from '../data/ndeliusIntegrationApiClient' +import { ErrorMessages, RadioButton } from '../data/uiModels' export default function nextAppointmentRoutes( router: Router, @@ -28,11 +20,12 @@ export default function nextAppointmentRoutes( get('/next-appointment/:id', async (req, res, next) => { await auditService.logPageView(Page.NEXT_APPOINTMENT, { who: res.locals.user.username, correlationId: req.id }) - const breachNoticeApiClient = new BreachNoticeApiClient( - await hmppsAuthClient.getSystemClientToken(res.locals.user.username), - ) + const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) + const breachNoticeApiClient = new BreachNoticeApiClient(token) + const ndeliusIntegrationApiClient = new NdeliusIntegrationApiClient(token) + const { id } = req.params - const nextAppointmentDetails = fetchNextAppointmentDetails() + const nextAppointmentDetails = await ndeliusIntegrationApiClient.getNextAppointmentDetails(id as string) const breachNotice: BreachNotice = await breachNoticeApiClient.getBreachNoticeById(id as string) const appointmentRadioButtons: Array = initiateNextAppointmentRadioButtonsAndApplySavedSelections( nextAppointmentDetails.futureAppointments, @@ -53,14 +46,15 @@ export default function nextAppointmentRoutes( }) post('/next-appointment/:id', async (req, res, next) => { - const breachNoticeApiClient = new BreachNoticeApiClient( - await hmppsAuthClient.getSystemClientToken(res.locals.user.username), - ) await auditService.logPageView(Page.NEXT_APPOINTMENT, { who: res.locals.user.username, correlationId: req.id }) + const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) + const ndeliusIntegrationApiClient = new NdeliusIntegrationApiClient(token) + const breachNoticeApiClient = new BreachNoticeApiClient(token) + const { id } = req.params let breachNotice: BreachNotice = null breachNotice = await breachNoticeApiClient.getBreachNoticeById(id as string) - const nextAppointmentDetails = fetchNextAppointmentDetails() + const nextAppointmentDetails = await ndeliusIntegrationApiClient.getNextAppointmentDetails(id as string) if (await commonUtils.redirectRequired(breachNotice, res)) return if (req.body.useContactNumber === 'No') { @@ -170,79 +164,5 @@ export default function nextAppointmentRoutes( return [officer.forename, officer.middleName, officer.surname].filter(item => item).join(', ') } - // Replace with API call to fetch future appointments - function fetchNextAppointmentDetails(): NextAppointmentDetails { - return { - responsibleOfficer: { - name: { - forename: 'ROForename', - middleName: 'ROMiddleName', - surname: 'ROSurname', - }, - telephoneNumber: '01234567891', - }, - futureAppointments: [ - { - contactId: 1, - datetime: LocalDateTime.now().toString(), - description: 'FIRSTAPPOINTMENT', - type: { - code: 'FTYPE', - description: 'First Appointment Type', - }, - location: createDummyLocation(), - officer: createDummyOfficer('first'), - }, - { - contactId: 2, - datetime: LocalDateTime.now().toString(), - description: 'SECONDAPPOINTMENT', - type: { - code: 'STYPE', - description: 'Second Appointment Type', - }, - location: createDummyLocation(), - officer: createDummyOfficer('second'), - }, - { - contactId: 3, - datetime: LocalDateTime.now().toString(), - description: 'THIRDAPPOINTMENT', - type: { - code: 'TTYPE', - description: 'Third Appointment Type', - }, - location: createDummyLocation(), - officer: createDummyOfficer('third'), - }, - ], - } - } - - function createDummyOfficer(variant: string): Officer { - return { - code: variant, - name: { - forename: `fname${variant}`, - middleName: `mname${variant}`, - surname: `sname${variant}`, - }, - } - } - - function createDummyLocation(): Address { - return { - addressId: 1, - type: 'Postal', - buildingName: null, - buildingNumber: '21', - county: 'Reply County', - district: 'Reply District', - postcode: 'NE22 3AA', - streetName: 'Reply Street', - townCity: 'Reply City', - } - } - return router } diff --git a/server/routes/reportDeleted.ts b/server/routes/reportDeleted.ts index caa11e6..fc57a58 100644 --- a/server/routes/reportDeleted.ts +++ b/server/routes/reportDeleted.ts @@ -1,11 +1,49 @@ import { type RequestHandler, Router } from 'express' import AuditService, { Page } from '../services/auditService' +import BreachNoticeApiClient from '../data/breachNoticeApiClient' import asyncMiddleware from '../middleware/asyncMiddleware' +import { HmppsAuthClient } from '../data' +import config from '../config' +import { HmppsDomainEvent } from '../data/hmppsSnsClient' +import SnsService from '../services/snsService' -export default function reportDeletedRoutes(router: Router, auditService: AuditService): Router { +export default function reportDeletedRoutes( + router: Router, + auditService: AuditService, + hmppsAuthClient: HmppsAuthClient, + snsService: SnsService, +): Router { const get = (path: string | string[], handler: RequestHandler) => router.get(path, asyncMiddleware(handler)) get('/report-deleted/:id', async (req, res, next) => { await auditService.logPageView(Page.REPORT_DELETED, { who: res.locals.user.username, correlationId: req.id }) + const breachNoticeApiClient = new BreachNoticeApiClient( + await hmppsAuthClient.getSystemClientToken(res.locals.user.username), + ) + const { id } = req.params + const breachNotice = await breachNoticeApiClient.getBreachNoticeById(id as string) + await breachNoticeApiClient.deleteBreachNotice(id as string) + + const baseUrl: string = config.apis.breachNotice.url + const event: HmppsDomainEvent = { + eventType: 'probation-case.breach-notice.deleted', + version: 1, + description: 'A breach notice has been deleted for a person on probation', + detailUrl: `${baseUrl}/report-deleted/${id}`, + occurredAt: new Date().toISOString(), + additionalInformation: { + breachNoticeId: `${id}`, + }, + personReference: { + identifiers: [ + { + type: 'CRN', + value: `${breachNotice.crn}`, + }, + ], + }, + } + + snsService.sendMessage(event) res.render('pages/report-deleted') }) return router diff --git a/server/routes/warningDetails.ts b/server/routes/warningDetails.ts index b6c7cc3..c8028a5 100644 --- a/server/routes/warningDetails.ts +++ b/server/routes/warningDetails.ts @@ -1,10 +1,282 @@ -import { Router } from 'express' +import { type RequestHandler, Router } from 'express' +import { LocalDate, LocalDateTime } from '@js-joda/core' +import BreachNoticeApiClient, { + BreachNotice, + BreachNoticeContact, + BreachNoticeRequirement, + WarningDetailsRequirementSelectItem, +} from '../data/breachNoticeApiClient' import AuditService, { Page } from '../services/auditService' +import { fromUserDate, toUserDate } from '../utils/dateUtils' +import { HmppsAuthClient } from '../data' +import CommonUtils from '../services/commonUtils' +import asyncMiddleware from '../middleware/asyncMiddleware' +import asArray from '../utils/utils' +import NdeliusIntegrationApiClient, { + EnforceableContact, + EnforceableContactList, + ReferenceData, + WarningDetails, +} from '../data/ndeliusIntegrationApiClient' +import logger from '../../logger' +import { ErrorMessages, SelectItem } from '../data/uiModels' -export default function warningDetailsRoutes(router: Router, auditService: AuditService): Router { - router.get('/warning-details/:id', async (req, res, next) => { +export default function warningDetailsRoutes( + router: Router, + auditService: AuditService, + hmppsAuthClient: HmppsAuthClient, + commonUtils: CommonUtils, +): Router { + const currentPage = 'warning-details' + const get = (path: string | string[], handler: RequestHandler) => router.get(path, asyncMiddleware(handler)) + const post = (path: string | string[], handler: RequestHandler) => router.post(path, asyncMiddleware(handler)) + + post('/warning-details/:id', async (req, res, next) => { + const { id } = req.params + const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username) + const breachNoticeApiClient = new BreachNoticeApiClient(token) + const ndeliusIntegrationApiClient = new NdeliusIntegrationApiClient(token) + const breachNotice = await breachNoticeApiClient.getBreachNoticeById(id as string) + + if (await commonUtils.redirectRequired(breachNotice, res)) return + + const warningDetails = await ndeliusIntegrationApiClient.getWarningDetails(breachNotice.crn, breachNotice.id) + + // failures recorded on this order + // list of contacts + const contactList: BreachNoticeContact[] = [] + + // for final warning theres only 1 of these for first warning screen + const failureRecordedContactId: string = req.body.failureRecordedContact + + // lookup the contact and push to the list of contacts to be saved + const enforceableContact: EnforceableContact = warningDetails.enforceableContacts.find( + contact => contact.id.toString() === failureRecordedContactId, + ) + + if (enforceableContact) { + contactList.push({ + id: null, + contactId: enforceableContact.id, + breachNoticeId: breachNotice.id, + contactDate: enforceableContact.datetime.toString(), + contactType: enforceableContact.type.description, + contactOutcome: enforceableContact.outcome.description, + }) + } + + // add the list of contacts to out breach notice + breachNotice.breachNoticeContactList = contactList + + // lookup the requirements + // this can come in an array or singular + breachNotice.breachNoticeRequirementList = asArray(req.body.failuresBeingEnforcedRequirements).map( + (requirementId: string) => { + const enforceableContactWithRequirement: EnforceableContact = warningDetails.enforceableContacts.find( + contact => contact.requirement.id.toString() === requirementId, + ) + const bodyParamBreachReason: string = `breachreason${requirementId}` + const currentRequirement: BreachNoticeRequirement = { + id: null, + breachNoticeId: breachNotice.id, + requirementId: enforceableContactWithRequirement.requirement.id, + requirementTypeMainCategoryDescription: enforceableContactWithRequirement.requirement.type.description, + requirementTypeSubCategoryDescription: enforceableContactWithRequirement.requirement.subType.description, + rejectionReason: req.body[bodyParamBreachReason], + fromDate: null, + toDate: null, + } + return applyContactDateRangesToRequirement(currentRequirement, warningDetails.enforceableContacts) + }, + ) + + const errorMessages: ErrorMessages = validateWarningDetails(breachNotice, req.body.responseRequiredByDate) + + const hasErrors: boolean = Object.keys(errorMessages).length > 0 + + // if we dont have validation errors navigate to ...next screen + if (!hasErrors) { + breachNotice.warningDetailsSaved = true + await breachNoticeApiClient.updateBreachNotice(id, breachNotice) + if (req.body.action === 'saveProgressAndClose') { + res.send(``) + } else if (req.body.action === 'refreshFromNdelius') { + // redirect to warning details to force a reload + res.redirect(`/warning-details/${id}`) + } else { + res.redirect(`/next-appointment/${id}`) + } + } else { + const failuresRecorded = createSelectItemListFromEnforceableContacts(warningDetails.enforceableContacts) + const breachReasons = convertReferenceDataListToSelectItemList(warningDetails.breachReasons) + const requirementsList = createFailuresBeingEnforcedRequirementSelectList( + warningDetails.enforceableContacts, + warningDetails.breachReasons, + breachNotice, + ) + + res.render(`pages/warning-details`, { + breachNotice, + warningDetails, + failuresRecorded, + breachReasons, + requirementsList, + currentPage, + errorMessages, + }) + } + }) + + function applyContactDateRangesToRequirement( + breachNoticeRequirement: BreachNoticeRequirement, + enforceableContactList: EnforceableContact[], + ): BreachNoticeRequirement { + const dateList = enforceableContactList + .filter(i => i.requirement.id.toString() === breachNoticeRequirement.requirementId.toString()) + .map(i => i.datetime) + const maxDate = dateList.reduce((a, b) => (a > b ? a : b)) + const minDate = dateList.reduce((a, b) => (a < b ? a : b)) + return { + ...breachNoticeRequirement, + fromDate: minDate.toString(), + toDate: maxDate.toString(), + } + } + + function validateWarningDetails(breachNotice: BreachNotice, responseRequiredByDate: string): ErrorMessages { + const errorMessages: ErrorMessages = {} + const currentDateAtStartOfTheDay: LocalDateTime = LocalDate.now().atStartOfDay() + try { + // eslint-disable-next-line no-param-reassign + breachNotice.responseRequiredDate = fromUserDate(responseRequiredByDate) + } catch (error: unknown) { + // this error will be thrown if a user inputs a non date format into the date field + logger.error(error) + // eslint-disable-next-line no-param-reassign + breachNotice.responseRequiredDate = responseRequiredByDate + errorMessages.responseRequiredByDate = { + text: 'The proposed date for this letter is in an invalid format, please use the correct format e.g 17/5/2024', + } + // we cant continue with date validation + return errorMessages + } + + if (breachNotice && breachNotice.responseRequiredDate) { + const localDateOfResponseRequiredByDate = LocalDate.parse(breachNotice.responseRequiredDate).atStartOfDay() + if (localDateOfResponseRequiredByDate.isBefore(currentDateAtStartOfTheDay)) { + errorMessages.responseRequiredByDate = { + text: 'The Response Required By Date can not be before today', + } + } + } + return errorMessages + } + + get('/warning-details/:id', async (req, res, next) => { await auditService.logPageView(Page.WARNING_DETAILS, { who: res.locals.user.username, correlationId: req.id }) - res.render(`pages/warning-details`) + const breachNoticeApiClient = new BreachNoticeApiClient( + await hmppsAuthClient.getSystemClientToken(res.locals.user.username), + ) + const breachNoticeId = req.params.id + const breachNotice = await breachNoticeApiClient.getBreachNoticeById(breachNoticeId as string) + if (await commonUtils.redirectRequired(breachNotice, res)) return + const ndeliusIntegrationApiClient = new NdeliusIntegrationApiClient( + await hmppsAuthClient.getSystemClientToken(res.locals.user.username), + ) + + const warningDetails: WarningDetails = await ndeliusIntegrationApiClient.getWarningDetails( + breachNotice.crn, + breachNotice.id, + ) + + const warningDetailsResponseRequiredDate: string = toUserDate(breachNotice.responseRequiredDate) + const breachReasons = convertReferenceDataListToSelectItemList(warningDetails.breachReasons) + const requirementsList = createFailuresBeingEnforcedRequirementSelectList( + warningDetails.enforceableContacts, + warningDetails.breachReasons, + breachNotice, + ) + + const failuresRecorded = createSelectItemListFromEnforceableContacts(warningDetails.enforceableContacts) + res.render(`pages/warning-details`, { + breachNotice, + warningDetails, + failuresRecorded, + breachReasons, + requirementsList, + currentPage, + warningDetailsResponseRequiredDate, + }) }) + + function createFailuresBeingEnforcedRequirementSelectList( + enforceableContactList: EnforceableContactList, + breachReasons: ReferenceData[], + breachNotice: BreachNotice, + ): WarningDetailsRequirementSelectItem[] { + if (enforceableContactList) { + return enforceableContactList.map((enforceableContact: EnforceableContact) => { + const { requirement } = enforceableContact + let breachNoticeRequirement: BreachNoticeRequirement = null + if (breachNotice.breachNoticeRequirementList) { + breachNoticeRequirement = breachNotice.breachNoticeRequirementList.find( + savedRequirement => savedRequirement.requirementId?.toString() === requirement.id?.toString(), + ) + } + const breachReasonSelectItems = craftTheBreachReasonSelectItems(breachReasons, breachNoticeRequirement) + return { + text: `${requirement.type.description} - ${requirement.subType.description}`, + value: requirement.id.toString(), + checked: !!breachNoticeRequirement, + conditional: { + html: `
+ + +
`, + }, + } + }) + } + return [] + } + + function createSelectItemListFromEnforceableContacts(enforceableContactList: EnforceableContactList): SelectItem[] { + const selectItemList: SelectItem[] = [] + if (enforceableContactList) { + enforceableContactList.forEach((enforceableContact: EnforceableContact) => { + const selectItem: SelectItem = { + text: enforceableContact.description, + value: enforceableContact.id.toString(), + selected: false, + } + selectItemList.push(selectItem) + }) + } + return selectItemList + } + + function craftTheBreachReasonSelectItems( + refDataList: ReferenceData[], + requirement: BreachNoticeRequirement, + ): SelectItem[] { + return refDataList.map((referenceData: ReferenceData) => { + return { + text: referenceData.description, + value: referenceData.code, + selected: requirement && requirement?.rejectionReason === referenceData.code, + } + }) + } + + function convertReferenceDataListToSelectItemList(referenceDataList: ReferenceData[]): SelectItem[] { + return referenceDataList.map(refData => ({ + selected: false, + text: refData.description, + value: refData.code, + })) + } + return router } diff --git a/server/routes/warningType.ts b/server/routes/warningType.ts index 8ecc56c..cbcbc71 100644 --- a/server/routes/warningType.ts +++ b/server/routes/warningType.ts @@ -1,15 +1,11 @@ import { type RequestHandler, Router } from 'express' import AuditService, { Page } from '../services/auditService' -import BreachNoticeApiClient, { - BreachNotice, - ErrorMessages, - RadioButton, - SelectItem, -} from '../data/breachNoticeApiClient' +import BreachNoticeApiClient, { BreachNotice } from '../data/breachNoticeApiClient' import NdeliusIntegrationApiClient, { SentenceType, WarningType } from '../data/ndeliusIntegrationApiClient' import { HmppsAuthClient } from '../data' import CommonUtils from '../services/commonUtils' import asyncMiddleware from '../middleware/asyncMiddleware' +import { ErrorMessages, RadioButton, SelectItem } from '../data/uiModels' export default function warningTypeRoutes( router: Router, @@ -54,11 +50,13 @@ export default function warningTypeRoutes( const breachNoticeApiClient = new BreachNoticeApiClient(token) const ndeliusIntegrationApiClient = new NdeliusIntegrationApiClient(token) const { id } = req.params - let breachNotice: BreachNotice = null - breachNotice = await breachNoticeApiClient.getBreachNoticeById(id as string) + const breachNotice: BreachNotice = await breachNoticeApiClient.getBreachNoticeById(id as string) if (await commonUtils.redirectRequired(breachNotice, res)) return const { warningTypes, sentenceTypes } = await ndeliusIntegrationApiClient.getWarningTypes(breachNotice.crn, id) - const sentenceTypeList: SelectItem[] = getSentenceTypeSelectItems(sentenceTypes) + const sentenceTypeList: SelectItem[] = initiateSentenceTypeSelectItemsAndApplySavedSelections( + sentenceTypes, + breachNotice, + ) const warningTypeRadioButtons: Array = initiateWarningTypeRadioButtonsAndApplySavedSelections( warningTypes, breachNotice, @@ -67,6 +65,14 @@ export default function warningTypeRoutes( // add new details breachNotice.breachNoticeTypeCode = req.body.warningType breachNotice.breachSentenceTypeCode = req.body.sentenceType + // find the condition Being Enforced + const conditionBeingEnforced: SentenceType = sentenceTypes.find( + sentenceType => sentenceType.code === breachNotice.breachSentenceTypeCode, + ) + if (conditionBeingEnforced) { + breachNotice.conditionBeingEnforced = conditionBeingEnforced.conditionBeingEnforced + } + const checkedButton: RadioButton = warningTypeRadioButtons.find(r => r.value === req.body.warningType) if (checkedButton) { breachNotice.breachNoticeTypeDescription = checkedButton.text @@ -125,40 +131,12 @@ export default function warningTypeRoutes( warningTypes: WarningType[], breachNotice: BreachNotice, ): RadioButton[] { - const warningTypeRadioButtons: RadioButton[] = [ + return [ ...warningTypes.map(warningType => ({ text: `${warningType.description}`, value: `${warningType.code}`, selected: false, - checked: false, - })), - ] - - if (breachNotice.breachNoticeTypeCode) { - warningTypeRadioButtons.forEach((radioButton: RadioButton) => { - if (breachNotice.breachNoticeTypeCode && breachNotice.breachNoticeTypeCode === radioButton.value) { - // eslint-disable-next-line no-param-reassign - radioButton.checked = true - } else { - // eslint-disable-next-line no-param-reassign - radioButton.checked = false - } - }) - } - return warningTypeRadioButtons - } - - function getSentenceTypeSelectItems(sentenceTypes: SentenceType[]): SelectItem[] { - return [ - { - text: 'Please Select', - value: '-1', - selected: true, - }, - ...sentenceTypes.map(sentenceType => ({ - text: `${sentenceType.description}`, - value: `${sentenceType.code}`, - selected: false, + checked: breachNotice.breachNoticeTypeCode && breachNotice.breachNoticeTypeCode === warningType.code, })), ] } @@ -167,7 +145,7 @@ export default function warningTypeRoutes( sentenceTypes: SentenceType[], breachNotice: BreachNotice, ): SelectItem[] { - const sentenceTypeSelectItems: SelectItem[] = [ + return [ { text: 'Please Select', value: '-1', @@ -176,23 +154,9 @@ export default function warningTypeRoutes( ...sentenceTypes.map(sentenceType => ({ text: `${sentenceType.description}`, value: `${sentenceType.code}`, - selected: false, + selected: breachNotice.breachSentenceTypeCode && breachNotice.breachSentenceTypeCode === sentenceType.code, })), ] - - if (breachNotice.breachSentenceTypeCode) { - sentenceTypeSelectItems.forEach((sentenceTypeSelectItem: SelectItem) => { - if ( - breachNotice.breachSentenceTypeCode && - breachNotice.breachSentenceTypeCode === sentenceTypeSelectItem.value - ) { - // eslint-disable-next-line no-param-reassign - sentenceTypeSelectItem.selected = true - } - }) - } - - return sentenceTypeSelectItems } return router diff --git a/server/utils/dateUtils.ts b/server/utils/dateUtils.ts index 8141547..9e1fdb9 100644 --- a/server/utils/dateUtils.ts +++ b/server/utils/dateUtils.ts @@ -9,7 +9,8 @@ export function fromUserDate(str: string): string { export function toUserDate(str: string): string { if (str) { - return DateTimeFormatter.ofPattern('d/M/yyyy').format(DateTimeFormatter.ISO_LOCAL_DATE.parse(str)) + const timeStrippedString = str.includes('T') ? str.split('T')[0] : str + return DateTimeFormatter.ofPattern('d/M/yyyy').format(DateTimeFormatter.ISO_LOCAL_DATE.parse(timeStrippedString)) } return '' } diff --git a/server/utils/nunjucksSetup.ts b/server/utils/nunjucksSetup.ts index bb7d2b7..64cb122 100644 --- a/server/utils/nunjucksSetup.ts +++ b/server/utils/nunjucksSetup.ts @@ -6,6 +6,7 @@ import fs from 'fs' import { initialiseName } from './utils' import config from '../config' import logger from '../../logger' +import { toUserDate } from './dateUtils' export default function nunjucksSetup(app: express.Express): void { app.set('view engine', 'njk') @@ -39,4 +40,5 @@ export default function nunjucksSetup(app: express.Express): void { njkEnv.addFilter('initialiseName', initialiseName) njkEnv.addFilter('assetMap', (url: string) => assetManifest[url] || url) + njkEnv.addFilter('toUserDate', toUserDate) } diff --git a/server/utils/utils.ts b/server/utils/utils.ts index ad5e441..c94e5dd 100644 --- a/server/utils/utils.ts +++ b/server/utils/utils.ts @@ -1,3 +1,4 @@ +import { ParsedQs } from 'qs' import { Name } from '../data/ndeliusIntegrationApiClient' const properCase = (word: string): string => @@ -27,3 +28,8 @@ export const initialiseName = (fullName?: string): string | null => { export function combineName(title: string, name: Name) { return [title, name.forename, name.middleName, name.surname].filter(n => n).join(' ') } + +export default function asArray(param: undefined | string | ParsedQs | (string | ParsedQs)[]): string[] { + if (param === undefined) return [] + return Array.isArray(param) ? (param as string[]) : [param as string] +} diff --git a/server/views/pages/check-your-report.njk b/server/views/pages/check-your-report.njk index 45d423c..9c71db3 100644 --- a/server/views/pages/check-your-report.njk +++ b/server/views/pages/check-your-report.njk @@ -321,14 +321,14 @@ Response Required By
- {% if breachNotice.responseRequiredByDate == null and breachNotice.warningTypeSaved %} + {% if breachNotice.responseRequiredDate == null and breachNotice.warningDetailsSaved %} enter required information {% else %} - {{ responseRequiredByDate }} + {{ responseRequiredDate }} {% endif %}
- {% if breachNotice.responseRequiredByDate == null %} + {% if breachNotice.responseRequiredDate == null %}
@@ -504,58 +504,58 @@
- {% if reportValidated %} - {{ govukButton({ - text: "Publish", - preventDoubleClick: "true", - type: "submit", - attributes: { - id: "publish" - } - }) }} - {{ govukButton({ - text: "View Draft Report", - classes: "govuk-button--secondary", - preventDoubleClick: "true", - href: '/pdf/'+breachNotice.id, - attributes: { - target: "_blank" - } - }) }} - {{ govukButton({ - text: "Delete draft", - href: '/report-deleted/'+breachNotice.id, - classes: "govuk-button--secondary", - preventDoubleClick: "true" - }) }} - {{ govukButton({ - text: "Close", - href: "/close", - classes: "govuk-button--secondary", - preventDoubleClick: "true" - }) }} + {% if reportValidated %} + {{ govukButton({ + text: "Publish", + preventDoubleClick: "true", + type: "submit", + attributes: { + id: "publish" + } + }) }} + {{ govukButton({ + text: "View Draft Report", + classes: "govuk-button--secondary", + preventDoubleClick: "true", + href: '/pdf/'+breachNotice.id, + attributes: { + target: "_blank" + } + }) }} + {{ govukButton({ + text: "Delete draft", + href: '/report-deleted/'+breachNotice.id, + classes: "govuk-button--secondary", + preventDoubleClick: "true" + }) }} + {{ govukButton({ + text: "Close", + href: "/close", + classes: "govuk-button--secondary", + preventDoubleClick: "true" + }) }} {% else %} - {{ govukButton({ - text: "View Draft Report", - classes: "govuk-button--secondary", - preventDoubleClick: "true", - href: '/pdf/'+breachNotice.id, - attributes: { - target: "_blank" - } - }) }} - {{ govukButton({ - text: "Delete draft", - href: '/report-deleted/'+breachNotice.id, - classes: "govuk-button--secondary", - preventDoubleClick: "true" - }) }} - {{ govukButton({ - text: "Close", - href: "/close", - classes: "govuk-button--secondary", - preventDoubleClick: "true" - }) }} + {{ govukButton({ + text: "View Draft Report", + classes: "govuk-button--secondary", + preventDoubleClick: "true", + href: '/pdf/'+breachNotice.id, + attributes: { + target: "_blank" + } + }) }} + {{ govukButton({ + text: "Delete draft", + href: '/report-deleted/'+breachNotice.id, + classes: "govuk-button--secondary", + preventDoubleClick: "true" + }) }} + {{ govukButton({ + text: "Close", + href: "/close", + classes: "govuk-button--secondary", + preventDoubleClick: "true" + }) }} {% endif %}
diff --git a/server/views/pages/next-appointment.njk b/server/views/pages/next-appointment.njk index cb2604e..1ccf97f 100644 --- a/server/views/pages/next-appointment.njk +++ b/server/views/pages/next-appointment.njk @@ -33,6 +33,7 @@
+ {% include "partials/review-required.njk" %}

Breach Notice - Next Appointment

diff --git a/server/views/pages/warning-details.njk b/server/views/pages/warning-details.njk index 4ef931c..57e54e7 100644 --- a/server/views/pages/warning-details.njk +++ b/server/views/pages/warning-details.njk @@ -5,6 +5,121 @@ {% block content %} -

placeholder

+ {% from "moj/components/side-navigation/macro.njk" import mojSideNavigation %} + {% from "govuk/components/radios/macro.njk" import govukRadios %} + {% from "govuk/components/select/macro.njk" import govukSelect %} + {% from "govuk/components/button/macro.njk" import govukButton %} + {% from "moj/components/date-picker/macro.njk" import mojDatePicker %} + {% from "govuk/components/table/macro.njk" import govukTable %} + {% from "govuk/components/input/macro.njk" import govukInput %} + {% from "govuk/components/checkboxes/macro.njk" import govukCheckboxes %} + {% from "govuk/components/details/macro.njk" import govukDetails %} + +
+ {% set errorList =[ + { text: errorMessages.responseRequiredByDate.text, href: "#responseRequiredByDate" } if errorMessages.responseRequiredByDate else None + ] | reject("undefined") %} + + {% if errorMessages | length > 0 %} + {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} + {{ govukErrorSummary({ titleText: "There is a problem", errorList: errorList }) }} + {% endif %} + + +
+
+ {% include "pages/side-nav.njk" %} +
+
+ {% include "partials/review-required.njk" %} +

{{ applicationName + " - " + breachNotice.breachNoticeTypeDescription + " Details" }}

+

+ Condition being enforced +

+

{{ breachNotice.conditionBeingEnforced }}

+ +

+ Failures recorded on this order +

+ {% for failure in warningDetails.enforceableContacts %} +

+ {{ failure.datetime | toUserDate }}, {{ failure.type.description }}, {{ failure.outcome.description }}, + + {{ govukDetails({ + summaryText: "View requirement notes", + text: failure.notes + }) }} +

+ {% endfor %} + + {{ govukCheckboxes({ + name: "failuresBeingEnforcedRequirements", + fieldset: { + legend: { + text: "Select the Failures being enforced", + isPageHeading: false, + classes: "govuk-fieldset__legend--m" + } + }, + hint: { + text: "Select all that apply" + }, + items: requirementsList + }) }} + + {{ mojDatePicker({ + id: "responseRequiredByDate", + name: "responseRequiredByDate", + value: warningDetailsResponseRequiredDate, + label: { + text: "Response Required By", + classes: "govuk-fieldset__legend--m" + }, + hint: { + text: "For example, 17/5/2024." + }, + errorMessage: { + text: errorMessages.responseRequiredByDate.text + } if errorMessages.responseRequiredByDate.text + }) }} + +
+ {{ govukButton({ + text: "Continue", + preventDoubleClick: "true", + type: "submit", + attributes: { + id: "continue-button" + } + }) }} + + {{ govukButton({ + text: "Refresh from Delius", + preventDoubleClick: "true", + type: "submit", + name: "action", + value: "refreshFromNdelius", + classes: "govuk-button--secondary", + attributes: { + id: "refresh-from-ndelius--button" + } + }) }} + + {{ govukButton({ + text: "Save Progress and Close", + preventDoubleClick: "true", + type: "submit", + name: "action", + value: "saveProgressAndClose", + classes: "govuk-button--secondary", + attributes: { + id: "close-button" + } + }) }} +
+
+
+
{% endblock %} diff --git a/server/views/partials/review-required.njk b/server/views/partials/review-required.njk index 862a6d6..fdcd62d 100644 --- a/server/views/partials/review-required.njk +++ b/server/views/partials/review-required.njk @@ -1,11 +1,24 @@ {%- from "moj/components/alert/macro.njk" import mojAlert -%} {% if breachNotice.reviewRequiredDate != null %} + + {% set messageHtml %} + {% if breachNotice.reviewEvent == 'MERGE' %} + A Merge occurred on {{ breachNotice.reviewRequiredDate | toUserDate() }} in NDelius and important details have chaged. This report should be reviewed before proceeding. Please confirm all information or discard this report. + {% endif %} + {% if breachNotice.reviewEvent == 'UNMERGE' %} + A Unmerge occurred on {{ breachNotice.reviewRequiredDate | toUserDate() }} in NDelius and important details have chaged. This report should be reviewed before proceeding. Please confirm all information or discard this report. + {% endif %} + {% endset -%} + {{ mojAlert({ variant: "warning", - title: "Review required", + title: "Review Required", showTitleAsHeading: true, dismissible: false, - html: 'Changes have been made in NDelius. Please review this report and delete if necessary.' + html: messageHtml, + attributes: { + id: "reviewAlert" + } }) }} {% endif %} diff --git a/wiremock/mappings/breach-notice-api.json b/wiremock/mappings/breach-notice-api.json index c0bc3d5..7c5aa8a 100644 --- a/wiremock/mappings/breach-notice-api.json +++ b/wiremock/mappings/breach-notice-api.json @@ -13,7 +13,7 @@ "titleAndFullName": null, "dateOfLetter": null, "referenceNumber": null, - "responseRequiredByDate": null, + "responseRequiredDate": null, "breachNoticeTypeCode": null, "breachNoticeTypeDescription": null, "breachConditionTypeCode": null, @@ -74,6 +74,76 @@ "status": 200 } }, + { + "request": { + "url": "/breach-notice-api/breach-notice/00000000-0000-0000-0000-000000000022", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "id": "00000000-0000-0000-0000-000000000022", + "crn": "X017052", + "titleAndFullName": null, + "dateOfLetter": null, + "referenceNumber": null, + "responseRequiredDate": null, + "breachNoticeTypeCode": null, + "breachNoticeTypeDescription": null, + "breachConditionTypeCode": null, + "breachConditionTypeDescription": null, + "breachSentenceTypeCode": null, + "breachSentenceTypeDescription": null, + "responsibleOfficer": null, + "contactNumber": null, + "nextAppointmentType": null, + "nextAppointmentDate": null, + "nextAppointmentLocation": null, + "nextAppointmentOfficer": null, + "nextAppointmentContact": null, + "completedDate": null, + "offenderAddress": { + "addressId": 12345, + "type": "Postal", + "buildingName": null, + "buildingNumber": "21", + "streetName": "Postal Street", + "district": "Postal District", + "townCity": "PostCity", + "county": "Postal County", + "postcode": "NE30 3ZZ" + }, + "replyAddress": { + "addressId": 33333, + "type": "Postal", + "buildingName": null, + "buildingNumber": "21", + "streetName": "Reply Street", + "district": "Reply District", + "townCity": "Reply City", + "county": "Reply County", + "postcode": "NE22 3AA" + }, + "basicDetailsSaved": null, + "warningTypeSaved": null, + "warningDetailsSaved": null, + "nextAppointmentSaved": null, + "useDefaultAddress": null, + "useDefaultReplyAddress": null, + "breachNoticeContactList": [ + { + "contactId": 1 + } + ], + "breachNoticeRequirementList": null, + "conditionBeingEnforced": "a condition being enforced" + }, + "headers": { + "Content-Type": "application/json;charset=UTF-8" + }, + "transformers": ["response-template"] + } + }, { "request": { "url": "/breach-notice-api/breach-notice/00000000-0000-0000-0000-000000000001", @@ -124,7 +194,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": null, "referenceNumber": null, - "responseRequiredByDate": null, + "responseRequiredDate": null, "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": null, @@ -189,7 +259,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": null, "referenceNumber": null, - "responseRequiredByDate": null, + "responseRequiredDate": null, "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": null, @@ -254,7 +324,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": null, "referenceNumber": null, - "responseRequiredByDate": null, + "responseRequiredDate": null, "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": null, @@ -319,7 +389,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": "2025-01-01", "referenceNumber": "REF0001", - "responseRequiredByDate": "2025-01-01", + "responseRequiredDate": "2025-01-01", "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": "BRE", @@ -401,7 +471,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": "2025-01-01", "referenceNumber": "REF0001", - "responseRequiredByDate": "2025-01-01", + "responseRequiredDate": "2025-01-01", "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": "BRE", @@ -474,7 +544,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": "2025-01-01", "referenceNumber": "REF0001", - "responseRequiredByDate": "2025-01-01", + "responseRequiredDate": "2025-01-01", "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": "BRE", @@ -547,7 +617,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": "2025-01-01", "referenceNumber": "REF0001", - "responseRequiredByDate": "2025-01-01", + "responseRequiredDate": "2025-01-01", "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": "BRE", @@ -620,7 +690,7 @@ "titleAndFullName": "Mr Billy The Kid", "dateOfLetter": "2025-01-01", "referenceNumber": "REF0001", - "responseRequiredByDate": "2025-01-01", + "responseRequiredDate": "2025-01-01", "breachNoticeTypeCode": "BW", "breachNoticeTypeDescription": "Breach Warning", "breachConditionTypeCode": "BRE", @@ -737,6 +807,82 @@ "useDefaultAddress": true, "useDefaultReplyAddress": true, "reviewRequiredDate": "2025-01-01T12:34:56", + "reviewEvent": "MERGE", + "breachNoticeContactList": [ + { + "contactId": 1 + } + ], + "breachNoticeRequirementList": [ + { + "requirementId": 1 + } + ] + }, + "headers": { + "Content-Type": "application/json;charset=UTF-8" + }, + "transformers": ["response-template"] + } + }, + { + "request": { + "url": "/breach-notice-api/breach-notice/00000000-0000-0000-0000-100000000102", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "id": "00000000-0000-0000-0000-100000000102", + "crn": "X100504", + "titleAndFullName": "Mr Billy The Kid", + "dateOfLetter": "2025-01-01", + "referenceNumber": "REF0001", + "responseRequiredByDate": "2025-01-01", + "breachNoticeTypeCode": "BW", + "breachNoticeTypeDescription": "Breach Warning", + "breachConditionTypeCode": "BRE", + "breachConditionTypeDescription": "Breach", + "breachSentenceTypeCode": "CO", + "breachSentenceTypeDescription": "Community Order(s)", + "responsibleOfficer": "An Officer", + "contactNumber": "0892929187", + "nextAppointmentType": "App Type", + "nextAppointmentDate": "2025-01-01T12:34:56", + "nextAppointmentLocation": "Location", + "nextAppointmentOfficer": "Officer", + "nextAppointmentContact": "0292827177", + "completedDate": null, + "offenderAddress": { + "addressId": 12345, + "type": "Postal", + "buildingName": null, + "buildingNumber": "21", + "streetName": "Postal Street", + "district": "Postal District", + "townCity": "PostCity", + "county": "Postal County", + "postcode": "NE30 3ZZ" + }, + "replyAddress": { + "addressId": 33333, + "type": "Postal", + "buildingName": null, + "buildingNumber": "21", + "streetName": "Reply Street", + "district": "Reply District", + "townCity": "Reply City", + "county": "Reply County", + "postcode": "NE22 3AA" + }, + "basicDetailsSaved": true, + "warningTypeSaved": true, + "warningDetailsSaved": true, + "nextAppointmentSaved": true, + "useDefaultAddress": true, + "useDefaultReplyAddress": true, + "reviewRequiredDate": "2025-01-01T12:34:56", + "reviewEvent": "UNMERGE", "breachNoticeContactList": [ { "contactId": 1 diff --git a/wiremock/mappings/ndelius-integration.json b/wiremock/mappings/ndelius-integration.json index 5a5f165..4e2da86 100644 --- a/wiremock/mappings/ndelius-integration.json +++ b/wiremock/mappings/ndelius-integration.json @@ -94,12 +94,12 @@ { "code": "CO", "description": "Community Order", - "conditionBeingEnforced": "string" + "conditionBeingEnforced": "sample condition being enforced" }, { "code": "123", "description": "Another Typer", - "conditionBeingEnforced": "string" + "conditionBeingEnforced": "another condition being enforced" } ], "defaultSentenceTypeCode": "CO" @@ -223,6 +223,58 @@ "Content-Type": "application/json;charset=UTF-8" } } + }, + { + "request": { + "urlPattern": "/next-appointment-details/(.*?)", + "method": "GET" + }, + "response": { + "status": 200, + "jsonBody": { + "responsibleOfficer": { + "name": { + "forename": "ROForename", + "middleName": "ROMiddleName", + "surname": "ROSurname" + }, + "telephoneNumber": "01234567891" + }, + "futureAppointments": [ + { + "contactId": 1, + "datetime": "2025-01-01T12:34:56", + "description": "FIRSTAPPOINTMENT", + "type": { + "code": "FTYPE", + "description": "First Appointment Type" + }, + "location": { + "addressId": 1, + "type": "Postal", + "buildingName": null, + "buildingNumber": "281", + "streetName": "Postal Street", + "townCity": "Postinton", + "district": "Postrict", + "county": "County Post", + "postcode": "NE30 3ZZ" + }, + "officer": { + "code": "N01A001", + "name": { + "forename": "Where", + "middleName": "is", + "surname": "Wally" + } + } + } + ] + }, + "headers": { + "Content-Type": "application/json;charset=UTF-8" + } + } } ] }