diff --git a/.github/workflows/access.yml b/.github/workflows/access.yml
index 6f53002e2c..6e6b41dcd4 100644
--- a/.github/workflows/access.yml
+++ b/.github/workflows/access.yml
@@ -58,6 +58,7 @@ on:
- '["ims-and-delius"]'
- '["appointment-reminders-and-delius"]'
- '["justice-email-and-delius"]'
+ - '["assess-for-early-release-and-delius"]'
# ^ add new projects here
# GitHub Actions doesn't support dynamic choices, we must add each project here to enable manual deployments
# See https://github.com/community/community/discussions/11795
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7afa312374..91ab71ace3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -85,6 +85,7 @@ jobs:
- ims-and-delius
- appointment-reminders-and-delius
- justice-email-and-delius
+ - assess-for-early-release-and-delius
# ^ add new projects here
# GitHub Actions doesn't support dynamic choices, we must add each project here to enable manual deployments
# See https://github.com/community/community/discussions/11795
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index b857195496..d05a39a339 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -73,6 +73,7 @@ on:
- '["ims-and-delius"]'
- '["appointment-reminders-and-delius"]'
- '["justice-email-and-delius"]'
+ - '["assess-for-early-release-and-delius"]'
# ^ add new projects here
# GitHub Actions doesn't support dynamic choices, we must add each project here to enable manual deployments
# See https://github.com/community/community/discussions/11795
diff --git a/.github/workflows/service-catalogue.yml b/.github/workflows/service-catalogue.yml
index 8250e66ce2..f99dd0b0d5 100644
--- a/.github/workflows/service-catalogue.yml
+++ b/.github/workflows/service-catalogue.yml
@@ -58,6 +58,7 @@ on:
- '["ims-and-delius"]'
- '["appointment-reminders-and-delius"]'
- '["justice-email-and-delius"]'
+ - '["assess-for-early-release-and-delius"]'
# ^ add new projects here
# GitHub Actions doesn't support dynamic choices, we must add each project here to enable manual deployments
# See https://github.com/community/community/discussions/11795
diff --git a/.idea/runConfigurations/assess-for-early-release-and-delius.run.xml b/.idea/runConfigurations/assess-for-early-release-and-delius.run.xml
new file mode 100644
index 0000000000..80e0aa569f
--- /dev/null
+++ b/.idea/runConfigurations/assess-for-early-release-and-delius.run.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index a1b558b2d9..88852a0e6c 100644
--- a/README.md
+++ b/README.md
@@ -61,9 +61,24 @@ Docker or remote dependencies.
To set up your development environment:
1. Open the project in [IntelliJ IDEA](https://www.jetbrains.com/idea/). Select "Import project from external model", then "Gradle".
-2. To run the tests for a service, right-click the `src/test` folder in the project view and select "Run tests" (See [Test](#test)).
+2. To run the tests for a service, right-click the `src/test` or `src/integrationTest` folder in the project view and
+ select "Run tests" (See [Test](#test)).
3. To start the service, use the pre-defined run configuration in `.idea/runConfigurations` (See [Run](#run)).
+## Code formatting
+
+Kotlin code is formatted using IntelliJ IDEA's code formatter,
+which follows the [Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html).
+
+GitHub Actions will automatically fix any formatting issues when you open a pull request.
+You can also use ⌘ ⌥ **L** (macOS),
+or **Ctrl+Alt+L** (Windows/Linux) to manually reformat your code in IntelliJ IDEA.
+See [Reformat code](https://www.jetbrains.com/help/idea/reformat-and-rearrange-code.html).
+
+Note: The code formatter does not remove unused imports by default. You should
+enable [Optimise on save](https://www.jetbrains.com/help/idea/creating-and-optimizing-imports.html#optimize-on-save) in
+your IntelliJ IDEA settings to ensure you do not commit unused imports.
+
# Build
IntelliJ will automatically build your code as needed. To build using Gradle, follow the instructions below.
@@ -97,15 +112,6 @@ To build Docker images locally, run:
./gradlew jibDockerBuild
```
-## Code formatting
-Kotlin code is formatted using IntelliJ IDEA's code formatter,
-which follows the [Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html).
-
-GitHub Actions will automatically fix any formatting issues when you open a pull request.
-You can also use ⌘ ⌥ **L** (macOS),
-or **Ctrl+Alt+L** (Windows/Linux) to manually reformat your code in IntelliJ IDEA.
-See [Reformat file](https://www.jetbrains.com/guide/java/tips/reformat-file/).
-
# Run
## IntelliJ
In IntelliJ IDEA, a Spring Boot [run configuration](https://www.jetbrains.com/help/idea/run-debug-configuration.html) is
diff --git a/doc/tech-docs/source/services.html.md.erb b/doc/tech-docs/source/services.html.md.erb
index 8225b8c9e8..cfee61c3ff 100644
--- a/doc/tech-docs/source/services.html.md.erb
+++ b/doc/tech-docs/source/services.html.md.erb
@@ -85,4 +85,5 @@ weight: 20
* [Ims And Delius](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/ims-and-delius)
* [Appointment Reminders And Delius](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/appointment-reminders-and-delius)
* [Justice Email And Delius](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/justice-email-and-delius)
+* [Assess For Early Release And Delius](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/assess-for-early-release-and-delius)
^ add new projects here
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 82dd18b204..eb1a55be0e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=57dafb5c2622c6cc08b993c85b7c06956a2f53536432a30ead46166dbca0f1e9
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
+distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/ProbationCaseDataLoader.kt b/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/ProbationCaseDataLoader.kt
index 2194d12f44..e3fedcc622 100644
--- a/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/ProbationCaseDataLoader.kt
+++ b/projects/approved-premises-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/ProbationCaseDataLoader.kt
@@ -101,14 +101,30 @@ class ProbationCaseDataLoader(
)
)
- listOf(
+ generateEventAndAddOffences(
ProbationCaseGenerator.CASE_COMPLEX,
+ eventId = 100001L,
+ mainOffence = Pair(200001L, LocalDate.parse("2024-10-11")),
+ additionalOffence = Pair(300001L, LocalDate.parse("2024-10-21"))
+ )
+ generateEventAndAddOffences(
ProbationCaseGenerator.CASE_X320741,
+ eventId = 100002L,
+ mainOffence = Pair(200002L, LocalDate.parse("2024-10-12")),
+ additionalOffence = Pair(300002L, LocalDate.parse("2024-10-22"))
+ )
+ generateEventAndAddOffences(
ProbationCaseGenerator.CASE_LAO_RESTRICTED,
+ eventId = 100003L,
+ mainOffence = Pair(200003L, LocalDate.parse("2024-10-13")),
+ additionalOffence = Pair(300003L, LocalDate.parse("2024-10-23"))
+ )
+ generateEventAndAddOffences(
ProbationCaseGenerator.CASE_LAO_EXCLUSION,
- ).forEach {
- generateEventAndAddOffences(probationCase = it)
- }
+ eventId = 100004L,
+ mainOffence = Pair(200004L, LocalDate.parse("2024-10-14")),
+ additionalOffence = Pair(300004L, LocalDate.parse("2024-10-24"))
+ )
personalCircumstanceTypeRepository.saveAll(PersonalCircumstanceGenerator.PC_TYPES)
personalCircumstanceSubTypeRepository.saveAll(PersonalCircumstanceGenerator.PC_SUB_TYPES)
@@ -125,17 +141,24 @@ class ProbationCaseDataLoader(
exclusionRepository.save(LimitedAccessGenerator.generateExclusion(EXCLUDED_CASE.toLimitedAccessPerson()))
}
- private fun generateEventAndAddOffences(probationCase: ProbationCase) {
+ private fun generateEventAndAddOffences(
+ probationCase: ProbationCase,
+ eventId: Long,
+ mainOffence: Pair,
+ additionalOffence: Pair,
+ ) {
val event = PersonGenerator.generateEvent(
"1",
- probationCase.id
+ probationCase.id,
+ id = eventId
).apply(eventRepository::save)
mainOffenceRepository.save(
OffenceGenerator.generateMainOffence(
event,
OffenceGenerator.OFFENCE_ONE,
- LocalDate.now().minusDays(7)
+ id = mainOffence.first,
+ date = mainOffence.second
)
)
@@ -143,7 +166,8 @@ class ProbationCaseDataLoader(
OffenceGenerator.generateAdditionalOffence(
event,
OffenceGenerator.OFFENCE_TWO,
- LocalDate.now().minusDays(5)
+ id = additionalOffence.first,
+ date = additionalOffence.second
)
)
}
diff --git a/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProbationCaseIntegrationTest.kt b/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProbationCaseIntegrationTest.kt
index ffa54129ac..23e75f272f 100644
--- a/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProbationCaseIntegrationTest.kt
+++ b/projects/approved-premises-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ProbationCaseIntegrationTest.kt
@@ -70,9 +70,15 @@ class ProbationCaseIntegrationTest {
assertThat(detail.mappaDetail?.level, equalTo(2))
assertThat(detail.registrations.map { it.description }, equalTo(listOf("Description of ARSO")))
val mainOffence = detail.offences.first { it.main }
+ assertThat(mainOffence.id, equalTo("M200001"))
assertThat(mainOffence.description, equalTo("Offence One"))
+ assertThat(mainOffence.date, equalTo(LocalDate.parse("2024-10-11")))
+ assertThat(mainOffence.eventId, equalTo(100001L))
val otherOffence = detail.offences.first { !it.main }
+ assertThat(otherOffence.id, equalTo("A300001"))
assertThat(otherOffence.description, equalTo("Offence Two"))
+ assertThat(otherOffence.date, equalTo(LocalDate.parse("2024-10-21")))
+ assertThat(otherOffence.eventId, equalTo(100001L))
assertThat(detail.careLeaver, equalTo(false))
assertThat(detail.veteran, equalTo(true))
}
diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt
index 951cdae8f7..97d91a0220 100644
--- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt
+++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/person/offence/entity/Offence.kt
@@ -88,7 +88,7 @@ interface MainOffenceRepository : JpaRepository {
@Query(
"""
select
- mo.offence.id as id,
+ mo.id as id,
mo.offence.code as code,
mo.offence.description as description,
mo.date as date,
@@ -99,7 +99,7 @@ interface MainOffenceRepository : JpaRepository {
where mo.event.personId = :personId and mo.event.active = true
union all
select
- ao.offence.id,
+ ao.id,
ao.offence.code,
ao.offence.description,
ao.date,
diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/CaseDetail.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/CaseDetail.kt
index b7359827c0..0cf9687caa 100644
--- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/CaseDetail.kt
+++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/model/CaseDetail.kt
@@ -59,7 +59,7 @@ data class MappaDetail(
)
data class Offence(
- val id: Long,
+ val id: String,
val code: String,
val description: String,
val date: LocalDate?,
diff --git a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/CaseService.kt b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/CaseService.kt
index 49cc6a0f32..8585a9cd9c 100644
--- a/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/CaseService.kt
+++ b/projects/approved-premises-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/CaseService.kt
@@ -87,7 +87,8 @@ fun CommunityManager.team() = Team(
team.endDate
)
-fun CaseOffence.asOffence() = Offence(id, code, description, date, main, eventId, eventNumber)
+fun CaseOffence.asOffence() =
+ Offence(id = if (main) "M$id" else "A$id", code, description, date, main, eventId, eventNumber)
fun Registration.asRegistration() = uk.gov.justice.digital.hmpps.model.Registration(type.code, type.description, date)
fun Registration.asMappa() = MappaDetail(
diff --git a/projects/assess-for-early-release-and-delius/.trivyignore b/projects/assess-for-early-release-and-delius/.trivyignore
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/projects/assess-for-early-release-and-delius/README.md b/projects/assess-for-early-release-and-delius/README.md
new file mode 100644
index 0000000000..e91a03d980
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/README.md
@@ -0,0 +1,3 @@
+# assess-for-early-release-and-delius
+
+// TODO Describe the service
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/applicationinsights.json b/projects/assess-for-early-release-and-delius/applicationinsights.json
new file mode 100644
index 0000000000..012edad194
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/applicationinsights.json
@@ -0,0 +1,62 @@
+{
+ "role": {
+ "name": "assess-for-early-release-and-delius"
+ },
+ "customDimensions": {
+ "service.version": "${VERSION}",
+ "service.team": "probation-integration"
+ },
+ "instrumentation": {
+ "logging": {
+ "level": "DEBUG"
+ },
+ "springScheduling": {
+ "enabled": false
+ }
+ },
+ "selfDiagnostics": {
+ "destination": "console"
+ },
+ "sampling": {
+ "percentage": 100
+ },
+ "preview": {
+ "sampling": {
+ "overrides": [
+ {
+ "telemetryType": "request",
+ "attributes": [
+ {
+ "key": "http.url",
+ "value": "https?://[^/]+/health/?.*",
+ "matchType": "regexp"
+ }
+ ],
+ "percentage": 0
+ },
+ {
+ "telemetryType": "request",
+ "attributes": [
+ {
+ "key": "http.url",
+ "value": "https?://[^/]+/info/?.*",
+ "matchType": "regexp"
+ }
+ ],
+ "percentage": 0
+ },
+ {
+ "telemetryType": "dependency",
+ "attributes": [
+ {
+ "key": "db.operation",
+ "value": "SELECT",
+ "matchType": "strict"
+ }
+ ],
+ "percentage": 10
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/build.gradle.kts b/projects/assess-for-early-release-and-delius/build.gradle.kts
new file mode 100644
index 0000000000..b7a0ac1c3d
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/build.gradle.kts
@@ -0,0 +1,39 @@
+import uk.gov.justice.digital.hmpps.extensions.ClassPathExtension
+
+apply(plugin = "com.google.cloud.tools.jib")
+
+dependencies {
+ implementation(project(":libs:audit"))
+ implementation(project(":libs:commons"))
+ implementation(project(":libs:oauth-server"))
+
+ implementation("org.springframework.boot:spring-boot-starter-actuator")
+ implementation("org.springframework.boot:spring-boot-starter-data-jpa")
+ implementation("org.springframework.boot:spring-boot-starter-security")
+ implementation("org.springframework.boot:spring-boot-starter-validation")
+ implementation("org.springframework.boot:spring-boot-starter-web")
+ implementation("org.springframework.boot:spring-boot-starter-data-ldap")
+ implementation("org.jetbrains.kotlin:kotlin-reflect")
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
+ implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
+ implementation(libs.springdoc)
+
+ dev(project(":libs:dev-tools"))
+ dev("com.unboundid:unboundid-ldapsdk")
+ dev("com.h2database:h2")
+ dev("org.testcontainers:oracle-xe")
+
+ runtimeOnly("com.oracle.database.jdbc:ojdbc11")
+
+ testImplementation("org.springframework.boot:spring-boot-starter-test")
+ testImplementation(libs.bundles.mockito)
+}
+
+configure {
+ jacocoExclusions = listOf(
+ "**/config/**",
+ "**/entity/**",
+ "**/ldap/**",
+ "**/AppKt.class"
+ )
+}
diff --git a/projects/assess-for-early-release-and-delius/deploy/Chart.yaml b/projects/assess-for-early-release-and-delius/deploy/Chart.yaml
new file mode 100644
index 0000000000..1c5c964515
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/deploy/Chart.yaml
@@ -0,0 +1,13 @@
+apiVersion: v2
+appVersion: '1.0'
+description: A Helm chart for Kubernetes
+name: assess-for-early-release-and-delius
+version: 1.0.0
+
+dependencies:
+ - name: generic-service
+ version: "3.2"
+ repository: https://ministryofjustice.github.io/hmpps-helm-charts
+ - name: generic-prometheus-alerts
+ version: "1.4"
+ repository: https://ministryofjustice.github.io/hmpps-helm-charts
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/deploy/database/access.yml b/projects/assess-for-early-release-and-delius/deploy/database/access.yml
new file mode 100644
index 0000000000..e8bd4437b7
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/deploy/database/access.yml
@@ -0,0 +1,9 @@
+database:
+ access:
+ username_key: /assess-for-early-release-and-delius/db-username
+ password_key: /assess-for-early-release-and-delius/db-password
+
+ audit:
+ username: AssessForEarlyReleaseAndDelius
+ forename: Assess for Early Release
+ surname: Service
diff --git a/projects/assess-for-early-release-and-delius/deploy/values-dev.yml b/projects/assess-for-early-release-and-delius/deploy/values-dev.yml
new file mode 100644
index 0000000000..1996330d35
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/deploy/values-dev.yml
@@ -0,0 +1,15 @@
+generic-service:
+ ingress:
+ host: assess-for-early-release-and-delius-dev.hmpps.service.justice.gov.uk
+
+ scheduledDowntime:
+ enabled: true
+
+ env:
+ SENTRY_ENVIRONMENT: dev
+ LOGGING_LEVEL_UK_GOV_DIGITAL_JUSTICE_HMPPS: DEBUG
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/issuer
+
+generic-prometheus-alerts:
+ businessHoursOnly: true
diff --git a/projects/assess-for-early-release-and-delius/deploy/values-preprod.yml b/projects/assess-for-early-release-and-delius/deploy/values-preprod.yml
new file mode 100644
index 0000000000..aa341f8d1c
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/deploy/values-preprod.yml
@@ -0,0 +1,14 @@
+generic-service:
+ ingress:
+ host: assess-for-early-release-and-delius-preprod.hmpps.service.justice.gov.uk
+
+ scheduledDowntime:
+ enabled: true
+
+ env:
+ SENTRY_ENVIRONMENT: preprod
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/issuer
+
+generic-prometheus-alerts:
+ businessHoursOnly: true
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/deploy/values-prod.yml b/projects/assess-for-early-release-and-delius/deploy/values-prod.yml
new file mode 100644
index 0000000000..a26f2ca45b
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/deploy/values-prod.yml
@@ -0,0 +1,10 @@
+enabled: false # TODO set this to true when you're ready to deploy your service
+
+generic-service:
+ ingress:
+ host: assess-for-early-release-and-delius.hmpps.service.justice.gov.uk
+
+ env:
+ SENTRY_ENVIRONMENT: prod
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json
+ SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/issuer
diff --git a/projects/assess-for-early-release-and-delius/deploy/values.yaml b/projects/assess-for-early-release-and-delius/deploy/values.yaml
new file mode 100644
index 0000000000..a47d12cc30
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/deploy/values.yaml
@@ -0,0 +1,26 @@
+# Common values
+generic-service:
+ productId: HMPPS518
+ nameOverride: assess-for-early-release-and-delius
+
+ image:
+ repository: ghcr.io/ministryofjustice/hmpps-probation-integration-services/assess-for-early-release-and-delius
+
+ ingress:
+ tlsSecretName: assess-for-early-release-and-delius-cert
+
+ namespace_secrets:
+ common:
+ SPRING_DATASOURCE_URL: DB_URL
+ SPRING_LDAP_URLS: LDAP_URL
+ SPRING_LDAP_USERNAME: LDAP_USERNAME
+ SPRING_LDAP_PASSWORD: LDAP_PASSWORD
+ assess-for-early-release-and-delius-database:
+ SPRING_DATASOURCE_USERNAME: DB_USERNAME
+ SPRING_DATASOURCE_PASSWORD: DB_PASSWORD
+ assess-for-early-release-and-delius-sentry:
+ SENTRY_DSN: SENTRY_DSN
+
+generic-prometheus-alerts:
+ targetApplication: assess-for-early-release-and-delius
+
diff --git a/projects/assess-for-early-release-and-delius/settings.gradle.kts b/projects/assess-for-early-release-and-delius/settings.gradle.kts
new file mode 100644
index 0000000000..54c13653a1
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "assess-for-early-release-and-delius"
diff --git a/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt
new file mode 100644
index 0000000000..df093edc47
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/DataLoader.kt
@@ -0,0 +1,92 @@
+package uk.gov.justice.digital.hmpps.data
+
+import jakarta.annotation.PostConstruct
+import jakarta.persistence.EntityManager
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
+import org.springframework.boot.context.event.ApplicationReadyEvent
+import org.springframework.context.ApplicationListener
+import org.springframework.stereotype.Component
+import org.springframework.transaction.annotation.Transactional
+import uk.gov.justice.digital.hmpps.data.generator.*
+import uk.gov.justice.digital.hmpps.user.AuditUserRepository
+
+@Component
+@ConditionalOnProperty("seed.database")
+class DataLoader(
+ private val auditUserRepository: AuditUserRepository,
+ private val entityManager: EntityManager
+) : ApplicationListener {
+
+ @PostConstruct
+ fun saveAuditUser() {
+ auditUserRepository.save(UserGenerator.AUDIT_USER)
+ }
+
+ @Transactional
+ override fun onApplicationEvent(are: ApplicationReadyEvent) {
+ entityManager.persist(ProviderGenerator.DEFAULT_PROVIDER)
+ entityManager.persist(StaffGenerator.PDUHEAD)
+ entityManager.persist(StaffGenerator.DEFAULT_PDUSTAFF_USER)
+ entityManager.persist(ProviderGenerator.DEFAULT_BOROUGH)
+ entityManager.persist(ProviderGenerator.DEFAULT_DISTRICT)
+
+ createOfficeLocationsAndDistricts()
+
+ entityManager.persist(ProviderGenerator.DEFAULT_TEAM)
+ entityManager.persist(ProviderGenerator.TEAM_ENDED_OR_NULL_LOCATIONS)
+
+ StaffGenerator.DEFAULT = StaffGenerator.generateStaff(
+ StaffGenerator.DEFAULT.code,
+ StaffGenerator.DEFAULT.forename,
+ StaffGenerator.DEFAULT.surname,
+ listOf(ProviderGenerator.DEFAULT_TEAM),
+ ProviderGenerator.DEFAULT_PROVIDER,
+ StaffGenerator.DEFAULT.middleName,
+ StaffGenerator.DEFAULT.user,
+ StaffGenerator.DEFAULT.id
+ )
+ entityManager.persist(StaffGenerator.DEFAULT)
+
+ entityManager.persist(StaffGenerator.DEFAULT_STAFF_USER)
+ entityManager.flush()
+
+ entityManager.persist(PersonGenerator.DEFAULT_PERSON)
+ entityManager.persist(PersonGenerator.PERSON_ENDED_TEAM_LOCATION)
+ entityManager.persist(PersonGenerator.DEFAULT_CM)
+ entityManager.persist(PersonGenerator.CM_ENDED_TEAM_LOCATION)
+
+ val person = PersonGenerator.generatePerson("N123456").also(entityManager::persist)
+ PersonGenerator.generateManager(person).also(entityManager::persist)
+
+ createCaseloadData()
+ }
+
+ private fun createOfficeLocationsAndDistricts() {
+ entityManager.persistAll(
+ ProviderGenerator.DISTRICT_BRK,
+ ProviderGenerator.DISTRICT_MKY,
+ ProviderGenerator.DISTRICT_OXF,
+ ProviderGenerator.LOCATION_BRK_1,
+ ProviderGenerator.LOCATION_BRK_2,
+ ProviderGenerator.LOCATION_ENDED,
+ ProviderGenerator.LOCATION_NULL
+ )
+ }
+
+ private fun createCaseloadData() {
+ entityManager.persistAll(
+ CaseloadGenerator.TEAM1,
+ CaseloadGenerator.STAFF1,
+ CaseloadGenerator.STAFF2,
+ CaseloadGenerator.CASELOAD_ROLE_OM_1,
+ CaseloadGenerator.CASELOAD_ROLE_OM_2,
+ CaseloadGenerator.CASELOAD_ROLE_OM_3,
+ CaseloadGenerator.CASELOAD_ROLE_OM_4,
+ CaseloadGenerator.CASELOAD_ROLE_OS_1
+ )
+ }
+
+ private fun EntityManager.persistAll(vararg entities: Any) {
+ entities.forEach { persist(it) }
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CaseloadGenerator.kt b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CaseloadGenerator.kt
new file mode 100644
index 0000000000..7fcf7c9bce
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/CaseloadGenerator.kt
@@ -0,0 +1,111 @@
+package uk.gov.justice.digital.hmpps.data.generator
+
+import uk.gov.justice.digital.hmpps.api.model.ManagedOffender
+import uk.gov.justice.digital.hmpps.api.model.Name
+import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator.DEFAULT_TEAM
+import uk.gov.justice.digital.hmpps.entity.Caseload
+import uk.gov.justice.digital.hmpps.entity.Staff
+import uk.gov.justice.digital.hmpps.entity.Team
+import uk.gov.justice.digital.hmpps.service.asStaff
+import uk.gov.justice.digital.hmpps.service.asTeam
+import java.time.LocalDate
+
+object CaseloadGenerator {
+
+ val STAFF1 = StaffGenerator.generateStaff("STCDE01", "Bob", "Smith")
+ val STAFF2 = StaffGenerator.generateStaff("STCDE02", "Joe", "Bloggs")
+
+ val TEAM1 = ProviderGenerator.generateTeam("N02BDT")
+
+ val CASELOAD_ROLE_OM_1 = generateCaseload(
+ staff = STAFF1,
+ team = DEFAULT_TEAM,
+ crn = "crn0001",
+ firstName = "John",
+ secondName = "x",
+ surname = "Brown",
+ allocationDate = LocalDate.of(2024, 1, 8),
+ roleCode = Caseload.CaseloadRole.OFFENDER_MANAGER.value
+ )
+
+ val CASELOAD_ROLE_OM_2 = generateCaseload(
+ staff = STAFF1,
+ team = DEFAULT_TEAM,
+ crn = "crn0022",
+ firstName = "Jane",
+ secondName = "y",
+ surname = "Doe",
+ allocationDate = LocalDate.of(2024, 1, 9),
+ roleCode = Caseload.CaseloadRole.OFFENDER_MANAGER.value
+ )
+
+ val CASELOAD_ROLE_OM_3 = generateCaseload(
+ staff = STAFF2,
+ team = DEFAULT_TEAM,
+ crn = "crn0077",
+ firstName = "Ano",
+ secondName = "no",
+ surname = "mys",
+ allocationDate = LocalDate.of(2024, 1, 10),
+ roleCode = Caseload.CaseloadRole.OFFENDER_MANAGER.value
+ )
+
+ val CASELOAD_ROLE_OM_4 = generateCaseload(
+ staff = STAFF2,
+ team = TEAM1,
+ crn = "crn0078",
+ firstName = "Joe",
+ secondName = "Denis",
+ surname = "Doe",
+ allocationDate = LocalDate.of(2024, 1, 10),
+ roleCode = Caseload.CaseloadRole.OFFENDER_MANAGER.value
+ )
+
+ val CASELOAD_ROLE_OS_1 = generateCaseload(
+ staff = STAFF2,
+ team = DEFAULT_TEAM,
+ crn = "crn0088",
+ firstName = "Robert",
+ secondName = "Alan",
+ surname = "Brown",
+ roleCode = Caseload.CaseloadRole.ORDER_SUPERVISOR.value
+ )
+
+ val MANAGED_OFFENDER = generateManagedOffender(CASELOAD_ROLE_OM_1, STAFF1, DEFAULT_TEAM)
+
+ fun generateCaseload(
+ staff: Staff,
+ team: Team,
+ allocationDate: LocalDate? = LocalDate.now(),
+ roleCode: String,
+ crn: String,
+ firstName: String,
+ secondName: String?,
+ surname: String,
+ startDate: LocalDate? = LocalDate.now(),
+ id: Long = IdGenerator.getAndIncrement()
+ ) = Caseload(
+ staff,
+ team,
+ allocationDate,
+ roleCode,
+ crn,
+ firstName,
+ secondName,
+ surname,
+ startDate,
+ id
+ )
+
+ fun generateManagedOffender(
+ caseload: Caseload,
+ staff: Staff,
+ team: Team
+ ) = ManagedOffender(
+ caseload.crn,
+ Name(caseload.firstName, caseload.secondName, caseload.surname),
+ caseload.allocationDate,
+ staff.asStaff(),
+ team.asTeam()
+ )
+}
diff --git a/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt
new file mode 100644
index 0000000000..c0f8fb2405
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/PersonGenerator.kt
@@ -0,0 +1,31 @@
+package uk.gov.justice.digital.hmpps.data.generator
+
+import uk.gov.justice.digital.hmpps.entity.*
+
+object PersonGenerator {
+ val DEFAULT_PERSON = generatePerson("T123456")
+ val PERSON_ENDED_TEAM_LOCATION = generatePerson("T123457")
+ val DEFAULT_CM = generateManager(DEFAULT_PERSON)
+ val CM_ENDED_TEAM_LOCATION =
+ generateManager(person = PERSON_ENDED_TEAM_LOCATION, team = ProviderGenerator.TEAM_ENDED_OR_NULL_LOCATIONS)
+
+ fun generatePerson(
+ crn: String,
+ softDeleted: Boolean = false,
+ id: Long = IdGenerator.getAndIncrement()
+ ) = Person(
+ crn,
+ softDeleted,
+ id
+ )
+
+ fun generateManager(
+ person: Person,
+ provider: Provider = ProviderGenerator.DEFAULT_PROVIDER,
+ team: Team = ProviderGenerator.DEFAULT_TEAM,
+ staff: Staff = StaffGenerator.DEFAULT,
+ softDeleted: Boolean = false,
+ active: Boolean = true,
+ id: Long = IdGenerator.getAndIncrement()
+ ) = PersonManager(person, provider, team, staff, softDeleted, active, id)
+}
diff --git a/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt
new file mode 100644
index 0000000000..a0e2885da0
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt
@@ -0,0 +1,141 @@
+package uk.gov.justice.digital.hmpps.data.generator
+
+import java.time.LocalDate
+
+object ProviderGenerator {
+ val DEFAULT_PROVIDER = generateProvider("N01")
+ val DEFAULT_BOROUGH = generateBorough("N01B")
+ val DEFAULT_DISTRICT = generateDistrict("N01D")
+ val DISTRICT_BRK = generateDistrict("TVP_BRK", "Berkshire")
+ val DISTRICT_OXF = generateDistrict("TVP_OXF", "Oxfordshire")
+ val DISTRICT_MKY = generateDistrict("TVP_MKY", "Milton Keynes")
+
+ val LOCATION_BRK_1 = generateLocation(
+ code = "TVP_BRK",
+ description = "Bracknell Office",
+ buildingNumber = "21",
+ streetName = "Some Place",
+ district = "District 1",
+ town = "Hearth",
+ postcode = "H34 7TH",
+ ldu = DISTRICT_BRK
+ )
+
+ val LOCATION_BRK_2 = generateLocation(
+ code = "TVP_RCC",
+ description = "Reading Office",
+ buildingNumber = "23",
+ buildingName = "The old hall",
+ streetName = "Another Place",
+ district = "District 2",
+ town = "Reading",
+ postcode = "RG1 3EH",
+ ldu = DISTRICT_BRK
+ )
+ val LOCATION_ENDED = generateLocation(
+ code = "TVP_RCC",
+ description = "Reading Office",
+ buildingNumber = "23",
+ buildingName = "The old hall",
+ streetName = "Another Place",
+ district = "District 2",
+ town = "Reading",
+ postcode = "RG1 3EH",
+ endDate = LocalDate.now().minusDays(1),
+ ldu = DISTRICT_BRK
+ )
+
+ val LOCATION_NULL = generateLocation(
+ code = "TVP_RCC",
+ description = "Null office",
+ ldu = DEFAULT_DISTRICT
+ )
+
+ val DEFAULT_TEAM = generateTeam("N01BDT")
+
+ val TEAM_ENDED_OR_NULL_LOCATIONS = generateTeam(
+ addresses = listOf(
+ LOCATION_BRK_1,
+ LOCATION_BRK_2,
+ LOCATION_ENDED,
+ LOCATION_NULL
+ ), code = "N01BDT"
+ )
+
+ fun generateProvider(
+ code: String,
+ description: String = "Description of $code",
+ id: Long = IdGenerator.getAndIncrement(),
+ endDate: LocalDate? = null
+ ) = uk.gov.justice.digital.hmpps.entity.Provider(code, description, id, endDate)
+
+ fun generateBorough(
+ code: String,
+ description: String = "Description of $code",
+ id: Long = IdGenerator.getAndIncrement(),
+ ) = uk.gov.justice.digital.hmpps.entity.Borough(code, description, id, listOf(), DEFAULT_PROVIDER)
+
+ fun generateDistrict(
+ code: String,
+ description: String = "Description of $code",
+ borough: uk.gov.justice.digital.hmpps.entity.Borough = DEFAULT_BOROUGH,
+ id: Long = IdGenerator.getAndIncrement()
+ ) = uk.gov.justice.digital.hmpps.entity.District(code, description, borough, id)
+
+ fun generateTeam(
+ code: String,
+ description: String = "Description of $code",
+ telephone: String? = "12345",
+ emailAddress: String? = "testemail",
+ district: uk.gov.justice.digital.hmpps.entity.District = DEFAULT_DISTRICT,
+ addresses: List = listOf(
+ LOCATION_BRK_1,
+ LOCATION_BRK_2
+ ),
+ startDate: LocalDate = LocalDate.now(),
+ endDate: LocalDate? = null,
+ id: Long = IdGenerator.getAndIncrement()
+ ) = uk.gov.justice.digital.hmpps.entity.Team(
+ code,
+ description,
+ telephone,
+ emailAddress,
+ district,
+ addresses,
+ startDate,
+ endDate,
+ id
+ )
+
+ fun generateLocation(
+ code: String,
+ description: String,
+ buildingName: String? = null,
+ buildingNumber: String? = null,
+ streetName: String? = null,
+ district: String? = null,
+ town: String? = null,
+ county: String? = null,
+ postcode: String? = null,
+ telephoneNumber: String? = null,
+ startDate: LocalDate = LocalDate.now(),
+ endDate: LocalDate? = null,
+ ldu: uk.gov.justice.digital.hmpps.entity.District,
+ id: Long = IdGenerator.getAndIncrement()
+ ) = uk.gov.justice.digital.hmpps.entity.OfficeLocation(
+ code,
+ description,
+ buildingName,
+ buildingNumber,
+ streetName,
+ district,
+ town,
+ county,
+ postcode,
+ telephoneNumber,
+ startDate,
+ endDate,
+ ldu,
+ id
+ )
+}
diff --git a/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt
new file mode 100644
index 0000000000..5f0a533eb3
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt
@@ -0,0 +1,30 @@
+package uk.gov.justice.digital.hmpps.data.generator
+
+import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator.DEFAULT_BOROUGH
+import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator.DEFAULT_PROVIDER
+import uk.gov.justice.digital.hmpps.entity.*
+import uk.gov.justice.digital.hmpps.set
+
+object StaffGenerator {
+ val PDUHEAD = generateStaff("N01BDT2", "Bob", "Smith").also { DEFAULT_BOROUGH.set(Borough::pduHeads, listOf(it)) }
+ val DEFAULT_PDUSTAFF_USER = generateStaffUser("bob-smith", PDUHEAD)
+ var DEFAULT = generateStaff("N01BDT1", "John", "Smith", teams = listOf(ProviderGenerator.DEFAULT_TEAM))
+ val DEFAULT_STAFF_USER = generateStaffUser("john-smith", DEFAULT)
+
+ fun generateStaff(
+ code: String,
+ forename: String,
+ surname: String,
+ teams: List = listOf(),
+ provider: Provider = DEFAULT_PROVIDER,
+ middleName: String? = null,
+ user: StaffUser? = null,
+ id: Long = IdGenerator.getAndIncrement()
+ ) = Staff(code, forename, surname, middleName, user, id, teams, provider).apply { user?.set("staff", this) }
+
+ fun generateStaffUser(
+ username: String,
+ staff: Staff? = null,
+ id: Long = IdGenerator.getAndIncrement()
+ ) = StaffUser(staff, username, id).apply { staff?.set("user", this) }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/UserGenerator.kt b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/UserGenerator.kt
new file mode 100644
index 0000000000..63e88c7b6c
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/UserGenerator.kt
@@ -0,0 +1,7 @@
+package uk.gov.justice.digital.hmpps.data.generator
+
+import uk.gov.justice.digital.hmpps.user.AuditUser
+
+object UserGenerator {
+ val AUDIT_USER = AuditUser(IdGenerator.getAndIncrement(), "AssessForEarlyReleaseAndDelius")
+}
diff --git a/projects/assess-for-early-release-and-delius/src/dev/resources/schema.ldif b/projects/assess-for-early-release-and-delius/src/dev/resources/schema.ldif
new file mode 100644
index 0000000000..9528a41c52
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/dev/resources/schema.ldif
@@ -0,0 +1,25 @@
+dn: ou=Users,dc=moj,dc=com
+objectclass: top
+objectclass: organizationalUnit
+ou: Users
+
+dn: cn=john-smith,ou=Users,dc=moj,dc=com
+objectclass: top
+objectclass: inetOrgPerson
+cn: john-smith
+sn: Staff
+mail: john.smith@moj.gov.uk
+telephoneNumber: 10101010101
+
+dn: cn=bob-smith,ou=Users,dc=moj,dc=com
+objectclass: top
+objectclass: inetOrgPerson
+cn: bob-smith
+sn: Staff
+mail: bob.smith@moj.gov.uk
+telephoneNumber: 20202020202
+
+dn: cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com
+description: Role Catalogue
+objectclass: top
+cn: ndRoleCatalogue
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CaseloadIntegrationTest.kt b/projects/assess-for-early-release-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CaseloadIntegrationTest.kt
new file mode 100644
index 0000000000..b8623570ac
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/CaseloadIntegrationTest.kt
@@ -0,0 +1,70 @@
+package uk.gov.justice.digital.hmpps
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import uk.gov.justice.digital.hmpps.api.model.ManagedOffender
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.CASELOAD_ROLE_OM_1
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.CASELOAD_ROLE_OM_2
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.CASELOAD_ROLE_OM_3
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.CASELOAD_ROLE_OM_4
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.STAFF1
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.STAFF2
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.TEAM1
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.generateManagedOffender
+import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator.DEFAULT_TEAM
+import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson
+import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken
+
+@AutoConfigureMockMvc
+@SpringBootTest(webEnvironment = RANDOM_PORT)
+internal class CaseloadIntegrationTest {
+ @Autowired
+ lateinit var mockMvc: MockMvc
+
+ @ParameterizedTest
+ @MethodSource("caseloadArgs")
+ fun getManagedOffenders(url: String, expected: List?) {
+ val res = mockMvc.perform(get(url).withToken()).andExpect(status().isOk)
+ .andReturn().response.contentAsJson>()
+ assertThat(res, equalTo(expected))
+ }
+
+ companion object {
+ @JvmStatic
+ fun caseloadArgs(): List = listOf(
+ Arguments.of("/staff/STCDEXX/caseload/managed-offenders", listOf()), Arguments.of(
+ "/staff/STCDE01/caseload/managed-offenders", listOf(
+ generateManagedOffender(CASELOAD_ROLE_OM_1, STAFF1, DEFAULT_TEAM),
+ generateManagedOffender(CASELOAD_ROLE_OM_2, STAFF1, DEFAULT_TEAM)
+ )
+ ), Arguments.of(
+ "/staff/STCDE02/caseload/managed-offenders", listOf(
+ generateManagedOffender(CASELOAD_ROLE_OM_3, STAFF2, DEFAULT_TEAM),
+ generateManagedOffender(CASELOAD_ROLE_OM_4, STAFF2, TEAM1)
+ )
+ ), Arguments.of(
+ "/team/N01BDT/caseload/managed-offenders", listOf(
+ generateManagedOffender(CASELOAD_ROLE_OM_3, STAFF2, DEFAULT_TEAM),
+ generateManagedOffender(CASELOAD_ROLE_OM_2, STAFF1, DEFAULT_TEAM),
+ generateManagedOffender(CASELOAD_ROLE_OM_1, STAFF1, DEFAULT_TEAM)
+ )
+ ), Arguments.of(
+ "/team/N02BDT/caseload/managed-offenders", listOf(
+ generateManagedOffender(CASELOAD_ROLE_OM_4, STAFF2, TEAM1)
+ )
+ ), Arguments.of(
+ "/team/N03BDT/caseload/managed-offenders", listOf()
+ )
+ )
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/assess-for-early-release-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt
new file mode 100644
index 0000000000..e7110210d7
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt
@@ -0,0 +1,192 @@
+package uk.gov.justice.digital.hmpps
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import uk.gov.justice.digital.hmpps.api.model.Manager
+import uk.gov.justice.digital.hmpps.api.model.OfficeAddress
+import uk.gov.justice.digital.hmpps.api.model.PDUHead
+import uk.gov.justice.digital.hmpps.api.model.StaffName
+import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator
+import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator
+import uk.gov.justice.digital.hmpps.data.generator.StaffGenerator
+import uk.gov.justice.digital.hmpps.entity.asAddress
+import uk.gov.justice.digital.hmpps.service.asManager
+import uk.gov.justice.digital.hmpps.service.asPDUHead
+import uk.gov.justice.digital.hmpps.service.asStaffName
+import uk.gov.justice.digital.hmpps.service.asTeam
+import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson
+import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withJson
+import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken
+
+@AutoConfigureMockMvc
+@SpringBootTest(webEnvironment = RANDOM_PORT)
+internal class IntegrationTest {
+ @Autowired
+ lateinit var mockMvc: MockMvc
+
+ @Test
+ fun `returns responsible officer details`() {
+ val crn = PersonGenerator.DEFAULT_PERSON.crn
+
+ val manager = mockMvc
+ .perform(get("/probation-case/$crn/responsible-community-manager").withToken())
+ .andExpect(status().isOk)
+ .andReturn().response.contentAsJson()
+
+
+ assertThat(
+ manager,
+ equalTo(
+ PersonGenerator.DEFAULT_CM.asManager().copy(
+ username = "john-smith", email = "john.smith@moj.gov.uk"
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `returns responsible officer details for a list of CRNs`() {
+ val crn = PersonGenerator.DEFAULT_PERSON.crn
+ mockMvc
+ .perform(post("/probation-case/responsible-community-manager").withToken().withJson(listOf(crn)))
+ .andExpect(status().isOk)
+ .andExpect(jsonPath("size()", equalTo(1)))
+ .andExpect(jsonPath("$[0].code", equalTo("N01BDT1")))
+ .andExpect(jsonPath("$[0].email", equalTo("john.smith@moj.gov.uk")))
+ }
+
+ @Test
+ fun `returns only active and non null team office locations`() {
+ val crn = PersonGenerator.PERSON_ENDED_TEAM_LOCATION.crn
+ val expectedAddresses: List = listOf(
+ ProviderGenerator.LOCATION_BRK_1.asAddress(),
+ ProviderGenerator.LOCATION_BRK_2.asAddress()
+ )
+ val manager = mockMvc
+ .perform(get("/probation-case/$crn/responsible-community-manager").withToken())
+ .andExpect(status().isOk)
+ .andReturn().response.contentAsJson()
+ assertThat(
+ manager,
+ equalTo(
+ PersonGenerator.CM_ENDED_TEAM_LOCATION.asManager().copy(
+ username = "john-smith", email = "john.smith@moj.gov.uk",
+ team = ProviderGenerator.TEAM_ENDED_OR_NULL_LOCATIONS.asTeam().copy(addresses = expectedAddresses)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `returns 404 if no crn or community officer`() {
+ mockMvc.perform(
+ get("/probation-case/Z123456/responsible-community-manager")
+ .withToken()
+ ).andExpect(status().isNotFound)
+ }
+
+ @Test
+ fun `returns staff details`() {
+ val username = StaffGenerator.DEFAULT_STAFF_USER.username
+ mockMvc
+ .perform(get("/staff/$username").withToken())
+ .andExpect(status().isOk)
+ .andExpect(jsonPath("$.username", equalTo("john-smith")))
+ .andExpect(jsonPath("$.email", equalTo("john.smith@moj.gov.uk")))
+ .andExpect(jsonPath("$.telephoneNumber", equalTo("10101010101")))
+ }
+
+ @Test
+ fun `username is case-insensitive`() {
+ val username = StaffGenerator.DEFAULT_STAFF_USER.username.uppercase()
+ mockMvc
+ .perform(get("/staff/$username").withToken())
+ .andExpect(status().isOk)
+ .andExpect(jsonPath("$.username", equalTo("john-smith")))
+ .andExpect(jsonPath("$.email", equalTo("john.smith@moj.gov.uk")))
+ .andExpect(jsonPath("$.telephoneNumber", equalTo("10101010101")))
+ }
+
+ @Test
+ fun `returns pdu heads`() {
+ val boroughCode = ProviderGenerator.DEFAULT_BOROUGH.code
+
+ val pduHeads = mockMvc
+ .perform(get("/staff/$boroughCode/pdu-head").withToken())
+ .andExpect(status().isOk)
+ .andReturn().response.contentAsJson>()
+
+ assertThat(
+ pduHeads,
+ equalTo(
+ listOf(
+ StaffGenerator.PDUHEAD.asPDUHead().copy(email = "bob.smith@moj.gov.uk")
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `returns staff names for usernames`() {
+ val usernames =
+ listOf(StaffGenerator.DEFAULT_PDUSTAFF_USER.username, StaffGenerator.DEFAULT_STAFF_USER.username)
+
+ val staffNames = mockMvc
+ .perform(post("/staff").withToken().withJson(usernames))
+ .andExpect(status().isOk)
+ .andReturn().response.contentAsJson>()
+
+ assertThat(
+ staffNames,
+ equalTo(
+ listOf(
+ StaffGenerator.PDUHEAD.asStaffName(),
+ StaffGenerator.DEFAULT.asStaffName()
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `usernames are case-insensitive`() {
+ val usernames = listOf(
+ StaffGenerator.DEFAULT_PDUSTAFF_USER.username.uppercase(),
+ StaffGenerator.DEFAULT_STAFF_USER.username.uppercase()
+ )
+
+ val staffNames = mockMvc
+ .perform(post("/staff").withToken().withJson(usernames))
+ .andExpect(status().isOk)
+ .andReturn().response.contentAsJson>()
+
+ assertThat(
+ staffNames,
+ equalTo(
+ listOf(
+ StaffGenerator.PDUHEAD.asStaffName(),
+ StaffGenerator.DEFAULT.asStaffName()
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `returns staff by code`() {
+ mockMvc
+ .perform(get("/staff/bycode/${StaffGenerator.DEFAULT.code}").withToken())
+ .andExpect(status().isOk)
+ .andExpect(jsonPath("$.username", equalTo("john-smith")))
+ .andExpect(jsonPath("$.email", equalTo("john.smith@moj.gov.uk")))
+ .andExpect(jsonPath("$.telephoneNumber", equalTo("10101010101")))
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/App.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/App.kt
new file mode 100644
index 0000000000..2c6b3789ed
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/App.kt
@@ -0,0 +1,12 @@
+package uk.gov.justice.digital.hmpps
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration
+import org.springframework.boot.runApplication
+
+@SpringBootApplication(exclude = [LdapRepositoriesAutoConfiguration::class])
+class App
+
+fun main(args: Array) {
+ runApplication(*args)
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/ManagedOffender.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/ManagedOffender.kt
new file mode 100644
index 0000000000..f3f02c708b
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/ManagedOffender.kt
@@ -0,0 +1,11 @@
+package uk.gov.justice.digital.hmpps.api.model
+
+import java.time.LocalDate
+
+data class ManagedOffender(
+ val crn: String,
+ val name: Name,
+ val allocationDate: LocalDate?,
+ val staff: Staff,
+ val team: Team?
+)
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Manager.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Manager.kt
new file mode 100644
index 0000000000..ce92154082
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Manager.kt
@@ -0,0 +1,30 @@
+package uk.gov.justice.digital.hmpps.api.model
+
+import java.time.LocalDate
+
+data class Manager(
+ val id: Long,
+ val code: String,
+ val name: Name,
+ val provider: Provider,
+ val team: Team,
+ val username: String?,
+ val email: String?,
+ val unallocated: Boolean
+)
+
+data class Name(val forename: String, val middleName: String?, val surname: String)
+data class Provider(val code: String, val description: String)
+data class Team(
+ val code: String, val description: String,
+ val telephone: String?,
+ val emailAddress: String?,
+ val addresses: List?,
+ val district: District,
+ val borough: Borough,
+ val startDate: LocalDate,
+ val endDate: LocalDate?
+)
+
+data class District(val code: String, val description: String, val borough: Borough)
+data class Borough(val code: String, val description: String)
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/OfficeAddress.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/OfficeAddress.kt
new file mode 100644
index 0000000000..ad1bd42347
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/OfficeAddress.kt
@@ -0,0 +1,56 @@
+package uk.gov.justice.digital.hmpps.api.model
+
+import java.time.LocalDate
+
+data class OfficeAddress(
+ val officeName: String,
+ val buildingName: String?,
+ val buildingNumber: String?,
+ val streetName: String?,
+ val district: String?,
+ val town: String?,
+ val county: String?,
+ val postcode: String?,
+ val ldu: String,
+ val telephoneNumber: String?,
+ val from: LocalDate,
+ val to: LocalDate?
+) {
+ companion object {
+ fun from(
+ officeName: String,
+ buildingName: String?,
+ buildingNumber: String?,
+ streetName: String?,
+ district: String?,
+ town: String?,
+ county: String?,
+ postcode: String?,
+ ldu: String,
+ telephoneNumber: String?,
+ from: LocalDate,
+ to: LocalDate?
+ ): OfficeAddress? =
+ if (
+ buildingName == null && buildingNumber == null && streetName == null &&
+ district == null && town == null && county == null && postcode == null
+ ) {
+ null
+ } else {
+ OfficeAddress(
+ officeName,
+ buildingName,
+ buildingNumber,
+ streetName,
+ district,
+ town,
+ county,
+ postcode,
+ ldu,
+ telephoneNumber,
+ from,
+ to
+ )
+ }
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Staff.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Staff.kt
new file mode 100644
index 0000000000..686fa9f596
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/Staff.kt
@@ -0,0 +1,30 @@
+package uk.gov.justice.digital.hmpps.api.model
+
+data class Staff(
+ val id: Long,
+ val code: String,
+ val name: Name,
+ val teams: List,
+ val provider: Provider,
+ val username: String?,
+ val email: String?,
+ val telephoneNumber: String?,
+ val unallocated: Boolean,
+)
+
+data class PDUHead(
+ val name: Name,
+ val email: String?
+)
+
+data class StaffName(
+ val id: Long,
+ val name: Name,
+ val code: String,
+ val username: String?
+)
+
+data class StaffEmail(
+ val code: String,
+ val email: String?
+)
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ProbationCaseResource.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ProbationCaseResource.kt
new file mode 100644
index 0000000000..ae30bb218d
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/ProbationCaseResource.kt
@@ -0,0 +1,19 @@
+package uk.gov.justice.digital.hmpps.api.resource
+
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.web.bind.annotation.*
+import uk.gov.justice.digital.hmpps.api.model.Manager
+import uk.gov.justice.digital.hmpps.api.model.StaffEmail
+import uk.gov.justice.digital.hmpps.service.ManagerService
+
+@RestController
+@RequestMapping("probation-case")
+@PreAuthorize("hasRole('PROBATION_API__ASSESS_FOR_EARLY_RELEASE__CASE_DETAIL')")
+class ProbationCaseResource(private val responsibleManagerService: ManagerService) {
+ @GetMapping("{crn}/responsible-community-manager")
+ fun findCommunityManager(@PathVariable crn: String): Manager = responsibleManagerService.findCommunityManager(crn)
+
+ @PostMapping("/responsible-community-manager")
+ fun findCommunityManagerEmails(@RequestBody crns: List): List =
+ responsibleManagerService.findCommunityManagerEmails(crns)
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/StaffResource.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/StaffResource.kt
new file mode 100644
index 0000000000..f46fdaaea5
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/StaffResource.kt
@@ -0,0 +1,33 @@
+package uk.gov.justice.digital.hmpps.api.resource
+
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.web.bind.annotation.*
+import uk.gov.justice.digital.hmpps.api.model.ManagedOffender
+import uk.gov.justice.digital.hmpps.api.model.PDUHead
+import uk.gov.justice.digital.hmpps.api.model.Staff
+import uk.gov.justice.digital.hmpps.api.model.StaffName
+import uk.gov.justice.digital.hmpps.service.StaffService
+
+@RestController
+@RequestMapping("staff")
+@PreAuthorize("hasRole('PROBATION_API__ASSESS_FOR_EARLY_RELEASE__CASE_DETAIL')")
+class StaffResource(
+ private val staffService: StaffService
+) {
+ @GetMapping("/{username}")
+ fun findStaff(@PathVariable username: String): Staff = staffService.findStaff(username)
+
+ @GetMapping("/bycode/{code}")
+ fun findStaffByCode(@PathVariable code: String): Staff = staffService.findStaffByCode(code)
+
+ @GetMapping("/{boroughCode}/pdu-head")
+ fun findPDUHead(@PathVariable boroughCode: String): List = staffService.findPDUHeads(boroughCode)
+
+ @PostMapping
+ fun findStaffForUsernames(@RequestBody usernames: List): List =
+ staffService.findStaffForUsernames(usernames)
+
+ @GetMapping("/{staffCode}/caseload/managed-offenders")
+ fun getManagedOffenders(@PathVariable staffCode: String): List =
+ staffService.getManagedOffenders(staffCode)
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/TeamResource.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/TeamResource.kt
new file mode 100644
index 0000000000..bd43321bd3
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/TeamResource.kt
@@ -0,0 +1,18 @@
+package uk.gov.justice.digital.hmpps.api.resource
+
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import uk.gov.justice.digital.hmpps.api.model.ManagedOffender
+import uk.gov.justice.digital.hmpps.service.TeamService
+
+@RestController
+@RequestMapping("team")
+@PreAuthorize("hasRole('PROBATION_API__ASSESS_FOR_EARLY_RELEASE__CASE_DETAIL')")
+class TeamResource(private val teamService: TeamService) {
+ @GetMapping("/{teamCode}/caseload/managed-offenders")
+ fun getManagedOffendersByTeam(@PathVariable teamCode: String): List =
+ teamService.getManagedOffendersByTeam(teamCode)
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Caseload.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Caseload.kt
new file mode 100644
index 0000000000..09c38f951c
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Caseload.kt
@@ -0,0 +1,47 @@
+package uk.gov.justice.digital.hmpps.entity
+
+import jakarta.persistence.*
+import org.hibernate.annotations.Immutable
+import org.springframework.data.jpa.domain.support.AuditingEntityListener
+import org.springframework.data.jpa.repository.JpaRepository
+import java.time.LocalDate
+
+interface CaseloadRepository : JpaRepository {
+ fun findByStaffCodeAndRoleCode(staffCode: String, role: String): List
+ fun findByTeamCodeAndRoleCodeOrderByAllocationDateDesc(staffCode: String, role: String): List
+}
+
+@Immutable
+@EntityListeners(AuditingEntityListener::class)
+@Entity
+@Table(name = "caseload")
+class Caseload(
+
+ @ManyToOne
+ @JoinColumn(name = "staff_employee_id")
+ val staff: Staff,
+
+ @ManyToOne
+ @JoinColumn(name = "trust_provider_team_id")
+ val team: Team,
+
+ val allocationDate: LocalDate?,
+ val roleCode: String,
+
+ @Column(columnDefinition = "char(7)")
+ val crn: String,
+
+ val firstName: String,
+ val secondName: String?,
+ val surname: String,
+ val startDate: LocalDate?,
+
+ @Id
+ val caseloadId: Long
+) {
+ enum class CaseloadRole(val value: String) {
+ OFFENDER_MANAGER("OM"),
+ ORDER_SUPERVISOR("OS")
+ }
+}
+
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/LdapUser.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/LdapUser.kt
new file mode 100644
index 0000000000..f19a3064b4
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/LdapUser.kt
@@ -0,0 +1,23 @@
+package uk.gov.justice.digital.hmpps.entity
+
+import org.springframework.ldap.odm.annotations.Attribute
+import org.springframework.ldap.odm.annotations.DnAttribute
+import org.springframework.ldap.odm.annotations.Entry
+import org.springframework.ldap.odm.annotations.Id
+import javax.naming.Name
+
+@Entry(objectClasses = ["inetOrgPerson", "top"])
+class LdapUser(
+ @Id
+ val dn: Name,
+
+ @Attribute(name = "cn")
+ @DnAttribute(value = "cn", index = 0)
+ val username: String,
+
+ @Attribute(name = "mail")
+ val email: String?,
+
+ @Attribute(name = "telephoneNumber")
+ val telephoneNumber: String?
+)
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt
new file mode 100644
index 0000000000..e1cd658bc8
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Person.kt
@@ -0,0 +1,25 @@
+package uk.gov.justice.digital.hmpps.entity
+
+import jakarta.persistence.Column
+import jakarta.persistence.Entity
+import jakarta.persistence.Id
+import jakarta.persistence.Table
+import org.hibernate.annotations.Immutable
+import org.hibernate.annotations.SQLRestriction
+
+@Immutable
+@Entity
+@Table(name = "offender")
+@SQLRestriction("soft_deleted = 0")
+class Person(
+
+ @Column(columnDefinition = "char(7)")
+ val crn: String,
+
+ @Column(name = "soft_deleted", columnDefinition = "number")
+ val softDeleted: Boolean = false,
+
+ @Id
+ @Column(name = "offender_id")
+ val id: Long
+)
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PersonManager.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PersonManager.kt
new file mode 100644
index 0000000000..6b6ec03fa3
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/PersonManager.kt
@@ -0,0 +1,52 @@
+package uk.gov.justice.digital.hmpps.entity
+
+import jakarta.persistence.*
+import org.hibernate.annotations.Immutable
+import org.hibernate.annotations.SQLRestriction
+import org.springframework.data.jpa.repository.EntityGraph
+import org.springframework.data.jpa.repository.JpaRepository
+import uk.gov.justice.digital.hmpps.exception.NotFoundException
+
+@Immutable
+@Entity
+@Table(name = "offender_manager")
+@SQLRestriction("soft_deleted = 0 and active_flag = 1")
+class PersonManager(
+
+ @ManyToOne
+ @JoinColumn(name = "offender_id")
+ val person: Person,
+
+ @ManyToOne
+ @JoinColumn(name = "probation_area_id")
+ val provider: Provider,
+
+ @ManyToOne
+ @JoinColumn(name = "team_id")
+ val team: Team,
+
+ @ManyToOne
+ @JoinColumn(name = "allocation_staff_id")
+ val staff: Staff,
+
+ @Column(name = "soft_deleted", columnDefinition = "number")
+ val softDeleted: Boolean = false,
+
+ @Column(name = "active_flag", columnDefinition = "number")
+ val active: Boolean = true,
+
+ @Id
+ @Column(name = "offender_manager_id")
+ val id: Long
+)
+
+interface PersonManagerRepository : JpaRepository {
+ @EntityGraph(attributePaths = ["person", "provider", "team", "staff.user"])
+ fun findByPersonCrn(crn: String): PersonManager?
+
+ @EntityGraph(attributePaths = ["person", "staff.user"])
+ fun findByPersonCrnIn(crn: List): List
+}
+
+fun PersonManagerRepository.getByCrn(crn: String) =
+ findByPersonCrn(crn) ?: throw NotFoundException("Person", "crn", crn)
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Provider.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Provider.kt
new file mode 100644
index 0000000000..898fed3843
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Provider.kt
@@ -0,0 +1,160 @@
+package uk.gov.justice.digital.hmpps.entity
+
+import jakarta.persistence.*
+import org.hibernate.annotations.Immutable
+import org.hibernate.annotations.SQLRestriction
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
+import uk.gov.justice.digital.hmpps.api.model.OfficeAddress
+import java.time.LocalDate
+
+@Immutable
+@Entity
+@Table(name = "probation_area")
+class Provider(
+ @Column(name = "code", columnDefinition = "char(3)")
+ val code: String,
+
+ val description: String,
+
+ @Id
+ @Column(name = "probation_area_id")
+ val id: Long,
+
+ @Column(name = "end_date")
+ var endDate: LocalDate? = null
+)
+
+@Immutable
+@Entity
+@Table(name = "team")
+class Team(
+
+ @Column(name = "code", columnDefinition = "char(6)")
+ val code: String,
+
+ val description: String,
+ val telephone: String?,
+ val emailAddress: String?,
+
+ @ManyToOne
+ @JoinColumn(name = "district_id")
+ val district: District,
+
+ @ManyToMany
+ @JoinTable(
+ name = "team_office_location",
+ joinColumns = [JoinColumn(name = "team_id")],
+ inverseJoinColumns = [JoinColumn(name = "office_location_id")]
+ )
+ @SQLRestriction("end_date is null or end_date > current_date")
+ val addresses: List,
+
+ val startDate: LocalDate,
+ val endDate: LocalDate?,
+
+ @Id
+ @Column(name = "team_id")
+ val id: Long
+)
+
+@Immutable
+@Entity
+@Table(name = "district")
+class District(
+
+ @Column(name = "code")
+ val code: String,
+
+ val description: String,
+
+ @ManyToOne
+ @JoinColumn(name = "borough_id")
+ val borough: Borough,
+
+ @Id
+ @Column(name = "district_id")
+ val id: Long
+)
+
+@Immutable
+@Entity
+@Table(name = "borough")
+class Borough(
+
+ @Column(name = "code")
+ val code: String,
+
+ val description: String,
+
+ @Id
+ @Column(name = "borough_id")
+ val id: Long,
+
+ @ManyToMany
+ @JoinTable(
+ name = "r_level_2_head_of_level_2",
+ joinColumns = [JoinColumn(name = "borough_id")],
+ inverseJoinColumns = [JoinColumn(name = "staff_id")]
+ )
+ val pduHeads: List,
+
+ @JoinColumn(name = "PROBATION_AREA_ID")
+ @OneToOne
+ val provider: Provider
+)
+
+interface BoroughRepository : JpaRepository {
+ @Query(
+ """
+ select b from Borough b
+ where b.code = :code
+ and (b.provider.endDate is null or b.provider.endDate > current_date)
+ """
+ )
+ fun findActiveByCode(code: String): Borough?
+}
+
+@Immutable
+@Entity
+@Table(name = "office_location")
+class OfficeLocation(
+
+ @Column(name = "code", columnDefinition = "char(7)")
+ val code: String,
+
+ val description: String,
+ val buildingName: String?,
+ val buildingNumber: String?,
+ val streetName: String?,
+ val district: String?,
+ val townCity: String?,
+ val county: String?,
+ val postcode: String?,
+ val telephoneNumber: String?,
+ val startDate: LocalDate,
+ val endDate: LocalDate?,
+
+ @JoinColumn(name = "district_id")
+ @ManyToOne
+ val ldu: District,
+
+ @Id
+ @Column(name = "office_location_id")
+ val id: Long
+)
+
+fun OfficeLocation.asAddress() = OfficeAddress(
+ description,
+ buildingName,
+ buildingNumber,
+ streetName,
+ district,
+ townCity,
+ county,
+ postcode,
+ ldu.description,
+ telephoneNumber,
+ startDate,
+ endDate
+)
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt
new file mode 100644
index 0000000000..397d6e5af8
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/entity/Staff.kt
@@ -0,0 +1,80 @@
+package uk.gov.justice.digital.hmpps.entity
+
+import jakarta.persistence.*
+import org.hibernate.annotations.Immutable
+import org.springframework.data.jpa.repository.EntityGraph
+import org.springframework.data.jpa.repository.JpaRepository
+import java.util.*
+
+@Immutable
+@Entity
+@Table(name = "staff")
+class Staff(
+
+ @Column(name = "officer_code", columnDefinition = "char(7)")
+ val code: String,
+
+ val forename: String,
+ val surname: String,
+
+ @Column(name = "forename2")
+ val middleName: String? = null,
+
+ @OneToOne(mappedBy = "staff")
+ val user: StaffUser? = null,
+
+ @Id
+ @Column(name = "staff_id")
+ val id: Long,
+
+ @ManyToMany
+ @JoinTable(
+ name = "staff_team",
+ joinColumns = [JoinColumn(name = "staff_id")],
+ inverseJoinColumns = [JoinColumn(name = "team_id")]
+ )
+ val teams: List?,
+
+ @ManyToOne
+ @JoinColumn(name = "probation_area_id") // Note: this column should not be used in general, because it can change whenever a user's teams changes. It's only used here for backward compatibility with Community API.
+ val provider: Provider,
+) {
+ fun isUnallocated() = code.endsWith("U")
+}
+
+@Entity
+@Immutable
+@Table(name = "user_")
+class StaffUser(
+
+ @OneToOne
+ @JoinColumn(name = "staff_id")
+ val staff: Staff? = null,
+
+ @Column(name = "distinguished_name")
+ val username: String,
+
+ @Id
+ @Column(name = "user_id")
+ val id: Long
+) {
+ @Transient
+ var email: String? = null
+
+ @Transient
+ var telephoneNumber: String? = null
+}
+
+interface StaffRepository : JpaRepository {
+ @EntityGraph(attributePaths = ["user", "teams"])
+ fun findByUserUsernameIgnoreCase(username: String): Staff?
+
+ @EntityGraph(attributePaths = ["user", "teams"])
+ fun findByCode(code: String): Staff?
+
+ @EntityGraph(attributePaths = ["user", "teams"])
+ override fun findById(id: Long): Optional
+
+ @EntityGraph(attributePaths = ["user"])
+ fun findByUserUsernameInIgnoreCase(usernames: List): List
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ManagerService.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ManagerService.kt
new file mode 100644
index 0000000000..bd0080fab4
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ManagerService.kt
@@ -0,0 +1,86 @@
+package uk.gov.justice.digital.hmpps.service
+
+import org.springframework.ldap.core.LdapTemplate
+import org.springframework.stereotype.Service
+import uk.gov.justice.digital.hmpps.api.model.*
+import uk.gov.justice.digital.hmpps.entity.Caseload
+import uk.gov.justice.digital.hmpps.entity.PersonManager
+import uk.gov.justice.digital.hmpps.entity.PersonManagerRepository
+import uk.gov.justice.digital.hmpps.entity.Staff
+import uk.gov.justice.digital.hmpps.exception.NotFoundException
+import uk.gov.justice.digital.hmpps.ldap.findEmailByUsername
+
+@Service
+class ManagerService(
+ private val ldapTemplate: LdapTemplate,
+ private val personManagerRepository: PersonManagerRepository
+) {
+ fun findCommunityManager(crn: String): Manager =
+ personManagerRepository.findByPersonCrn(crn)?.let { ro ->
+ ro.staff.user?.apply {
+ email = ldapTemplate.findEmailByUsername(username)
+ }
+ ro.asManager()
+ } ?: throw NotFoundException("CommunityManager", "crn", crn)
+
+ fun findCommunityManagerEmails(crns: List): List =
+ personManagerRepository.findByPersonCrnIn(crns).map {
+ StaffEmail(it.staff.code, it.staff.user?.username?.let { ldapTemplate.findEmailByUsername(it) })
+ }
+}
+
+fun PersonManager.asManager() = Manager(
+ staff.id,
+ staff.code,
+ staff.name(),
+ provider.asProvider(),
+ team.asTeam(),
+ staff.user?.username,
+ staff.user?.email,
+ staff.isUnallocated()
+)
+
+fun Staff.name() = Name(forename, middleName, surname)
+fun uk.gov.justice.digital.hmpps.entity.Provider.asProvider() =
+ uk.gov.justice.digital.hmpps.api.model.Provider(code, description)
+
+fun uk.gov.justice.digital.hmpps.entity.Team.asTeam() = uk.gov.justice.digital.hmpps.api.model.Team(
+ code,
+ description,
+ telephone,
+ emailAddress,
+ addresses.mapNotNull(uk.gov.justice.digital.hmpps.entity.OfficeLocation::asTeamAddress),
+ district.asDistrict(),
+ district.borough.asBorough(),
+ startDate,
+ endDate
+)
+
+fun uk.gov.justice.digital.hmpps.entity.OfficeLocation.asTeamAddress() = OfficeAddress.from(
+ description,
+ buildingName,
+ buildingNumber,
+ streetName,
+ district,
+ townCity,
+ county,
+ postcode,
+ ldu.description,
+ telephoneNumber,
+ startDate,
+ endDate
+)
+
+fun Caseload.asManagedOffender() = ManagedOffender(
+ crn,
+ Name(firstName, secondName, surname),
+ allocationDate,
+ staff.asStaff(),
+ team.asTeam()
+)
+
+fun uk.gov.justice.digital.hmpps.entity.District.asDistrict() =
+ uk.gov.justice.digital.hmpps.api.model.District(code, description, borough.asBorough())
+
+fun uk.gov.justice.digital.hmpps.entity.Borough.asBorough() =
+ uk.gov.justice.digital.hmpps.api.model.Borough(code, description)
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt
new file mode 100644
index 0000000000..ff81f505da
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/StaffService.kt
@@ -0,0 +1,81 @@
+package uk.gov.justice.digital.hmpps.service
+
+import org.springframework.ldap.core.LdapTemplate
+import org.springframework.stereotype.Service
+import uk.gov.justice.digital.hmpps.api.model.ManagedOffender
+import uk.gov.justice.digital.hmpps.api.model.PDUHead
+import uk.gov.justice.digital.hmpps.api.model.Staff
+import uk.gov.justice.digital.hmpps.api.model.StaffName
+import uk.gov.justice.digital.hmpps.entity.BoroughRepository
+import uk.gov.justice.digital.hmpps.entity.Caseload.CaseloadRole
+import uk.gov.justice.digital.hmpps.entity.CaseloadRepository
+import uk.gov.justice.digital.hmpps.entity.LdapUser
+import uk.gov.justice.digital.hmpps.entity.StaffRepository
+import uk.gov.justice.digital.hmpps.exception.NotFoundException
+import uk.gov.justice.digital.hmpps.ldap.findByUsername
+
+@Service
+class StaffService(
+ private val ldapTemplate: LdapTemplate,
+ private val staffRepository: StaffRepository,
+ private val boroughRepository: BoroughRepository,
+ private val caseloadRepository: CaseloadRepository,
+) {
+ fun findStaff(username: String): Staff = staffRepository.findByUserUsernameIgnoreCase(username)
+ ?.let { ldapTemplate.populateUserDetails(it).asStaff() }
+ ?: throw NotFoundException("Staff", "username", username)
+
+ fun findStaffByCode(code: String): Staff = staffRepository.findByCode(code)
+ ?.let { ldapTemplate.populateUserDetails(it).asStaff() }
+ ?: throw NotFoundException("Staff", "code", code)
+
+ fun findPDUHeads(boroughCode: String): List = boroughRepository.findActiveByCode(boroughCode)?.pduHeads
+ ?.map { ldapTemplate.populateUserDetails(it).asPDUHead() }
+ ?: listOf()
+
+ fun findStaffForUsernames(usernames: List): List =
+ staffRepository.findByUserUsernameInIgnoreCase(usernames).map { it.asStaffName() }
+
+ fun getManagedOffenders(staffCode: String): List =
+ caseloadRepository.findByStaffCodeAndRoleCode(
+ staffCode,
+ CaseloadRole.OFFENDER_MANAGER.value
+ ).map {
+ it.asManagedOffender()
+ }
+
+ private fun LdapTemplate.populateUserDetails(staff: uk.gov.justice.digital.hmpps.entity.Staff) =
+ staff.apply {
+ user?.apply {
+ ldapTemplate.findByUsername(username)?.let {
+ email = it.email
+ telephoneNumber = it.telephoneNumber
+ }
+ }
+ }
+}
+
+fun uk.gov.justice.digital.hmpps.entity.Staff.asStaff() = Staff(
+ id,
+ code,
+ name(),
+ teams?.map { it.asTeam() } ?: listOf(),
+ provider.asProvider(),
+ user?.username,
+ user?.email,
+ user?.telephoneNumber,
+ isUnallocated()
+)
+
+fun uk.gov.justice.digital.hmpps.entity.Staff.asPDUHead() = PDUHead(
+ name(),
+ user?.email
+)
+
+fun uk.gov.justice.digital.hmpps.entity.Staff.asStaffName() = StaffName(
+ id,
+ name(),
+ code,
+ user?.username
+)
+
diff --git a/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/TeamService.kt b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/TeamService.kt
new file mode 100644
index 0000000000..4ae1983679
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/TeamService.kt
@@ -0,0 +1,19 @@
+package uk.gov.justice.digital.hmpps.service
+
+import org.springframework.stereotype.Service
+import uk.gov.justice.digital.hmpps.api.model.ManagedOffender
+import uk.gov.justice.digital.hmpps.entity.Caseload.CaseloadRole
+import uk.gov.justice.digital.hmpps.entity.CaseloadRepository
+
+@Service
+class TeamService(
+ private val caseloadRepository: CaseloadRepository,
+) {
+ fun getManagedOffendersByTeam(teamCode: String): List =
+ caseloadRepository.findByTeamCodeAndRoleCodeOrderByAllocationDateDesc(
+ teamCode,
+ CaseloadRole.OFFENDER_MANAGER.value
+ ).map {
+ it.asManagedOffender()
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/main/resources/application.yml b/projects/assess-for-early-release-and-delius/src/main/resources/application.yml
new file mode 100644
index 0000000000..0a4dd92c9e
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/main/resources/application.yml
@@ -0,0 +1,80 @@
+# Default config
+server.shutdown: graceful
+spring:
+ jackson:
+ default-property-inclusion: non_null
+ jpa:
+ hibernate.ddl-auto: validate
+ database-platform: org.hibernate.dialect.OracleDialect
+ properties:
+ hibernate:
+ timezone.default_storage: NORMALIZE
+ query.mutation_strategy: org.hibernate.query.sqm.mutation.internal.inline.InlineMutationStrategy
+ query.mutation_strategy.persistent:
+ create_tables: false
+ drop_tables: false
+ query.mutation_strategy.global_temporary:
+ create_tables: false
+ drop_tables: false
+ ldap:
+ base: ou=Users,dc=moj,dc=com
+ base-environment:
+ java.naming.ldap.derefAliases: never
+ threads.virtual.enabled: true
+
+oauth2.roles:
+ - PROBATION_API__ASSESS_FOR_EARLY_RELEASE__CASE_DETAIL
+
+springdoc.default-produces-media-type: application/json
+
+delius.db.username: AssessForEarlyReleaseAndDelius # Should match value in [deploy/database/access.yml].
+
+management:
+ endpoints.web:
+ base-path: /
+ exposure.include: [ "health", "info" ]
+ endpoint.health.show-details: always
+
+info.productId: HMPPS518 # https://developer-portal.hmpps.service.justice.gov.uk/products/185
+
+---
+# Shared dev/test config
+spring.config.activate.on-profile: [ "dev", "integration-test" ]
+server.shutdown: immediate
+
+spring:
+ datasource.url: jdbc:h2:file:./dev;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH;AUTO_SERVER=true;AUTO_SERVER_PORT=9092
+ jpa.hibernate.ddl-auto: create-drop
+ ldap.embedded:
+ validation.enabled: false
+ base-dn: ${spring.ldap.base}
+
+seed.database: true
+wiremock.enabled: true
+context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockInitialiser
+
+logging.level:
+ uk.gov.justice.digital.hmpps: DEBUG
+ org.hibernate.tool.schema: ERROR
+ org.apache.activemq: WARN
+
+---
+spring.config.activate.on-profile: integration-test
+spring.datasource.url: jdbc:h2:mem:./test;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH
+
+---
+spring.config.activate.on-profile: oracle
+spring:
+ datasource.url: 'jdbc:tc:oracle:slim-faststart:///XEPDB1'
+ jpa.hibernate.ddl-auto: create
+
+---
+spring.config.activate.on-profile: delius-db
+spring:
+ datasource:
+ url: 'jdbc:oracle:thin:@//localhost:1521/XEPDB1'
+ username: delius_pool
+ password: NDelius1
+ jpa.hibernate.ddl-auto: validate
+seed.database: false
+delius.db.username: NationalUser
diff --git a/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/api/resource/StaffResourceTest.kt b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/api/resource/StaffResourceTest.kt
new file mode 100644
index 0000000000..f69de1ccf1
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/api/resource/StaffResourceTest.kt
@@ -0,0 +1,31 @@
+package uk.gov.justice.digital.hmpps.api.resource
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.whenever
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator.MANAGED_OFFENDER
+import uk.gov.justice.digital.hmpps.service.StaffService
+
+@ExtendWith(MockitoExtension::class)
+internal class StaffResourceTest {
+
+ @Mock
+ lateinit var staffService: StaffService
+
+ @InjectMocks
+ lateinit var resource: StaffResource
+
+ @Test
+ fun `calls managed offenders endpoint`() {
+ whenever(staffService.getManagedOffenders("STCDE01")).thenReturn(
+ listOf(MANAGED_OFFENDER)
+ )
+ val res = resource.getManagedOffenders("STCDE01")
+ assertThat(res[0].crn, equalTo("crn0001"))
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/api/resource/TeamResourceTest.kt b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/api/resource/TeamResourceTest.kt
new file mode 100644
index 0000000000..17b93d64ad
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/api/resource/TeamResourceTest.kt
@@ -0,0 +1,33 @@
+package uk.gov.justice.digital.hmpps.api.resource
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.whenever
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator
+import uk.gov.justice.digital.hmpps.service.TeamService
+
+@ExtendWith(MockitoExtension::class)
+internal class TeamResourceTest {
+
+ @Mock
+ lateinit var teamService: TeamService
+
+ @InjectMocks
+ lateinit var resource: TeamResource
+
+ @Test
+ fun `calls managed offenders endpoint`() {
+ whenever(teamService.getManagedOffendersByTeam("N01BDT")).thenReturn(
+ listOf(
+ CaseloadGenerator.MANAGED_OFFENDER
+ )
+ )
+ val res = resource.getManagedOffendersByTeam("N01BDT")
+ assertThat(res[0].crn, equalTo("crn0001"))
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/ResponsibleManagerServiceTest.kt b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/ResponsibleManagerServiceTest.kt
new file mode 100644
index 0000000000..b71c91ee74
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/ResponsibleManagerServiceTest.kt
@@ -0,0 +1,44 @@
+package uk.gov.justice.digital.hmpps.service
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import org.springframework.ldap.core.AttributesMapper
+import org.springframework.ldap.core.LdapTemplate
+import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator
+import uk.gov.justice.digital.hmpps.data.generator.StaffGenerator
+import uk.gov.justice.digital.hmpps.entity.PersonManagerRepository
+
+@ExtendWith(MockitoExtension::class)
+internal class ResponsibleManagerServiceTest {
+
+ @Mock
+ lateinit var personManagerRepository: PersonManagerRepository
+
+ @Mock
+ lateinit var ldapTemplate: LdapTemplate
+
+ @InjectMocks
+ lateinit var service: ManagerService
+
+ @Test
+ fun `does not call ldap when no staff user`() {
+ val person = PersonGenerator.generatePerson("L123456")
+ val staff = StaffGenerator.generateStaff("NoLdap", "No", "User")
+ val cm = PersonGenerator.generateManager(person, staff = staff)
+
+ whenever(personManagerRepository.findByPersonCrn(person.crn)).thenReturn(cm)
+
+ val res = service.findCommunityManager(person.crn)
+ assertThat(res, equalTo(cm.asManager()))
+ verify(ldapTemplate, never()).search(any(), any>())
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/StaffServiceTest.kt b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/StaffServiceTest.kt
new file mode 100644
index 0000000000..873d007057
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/StaffServiceTest.kt
@@ -0,0 +1,43 @@
+package uk.gov.justice.digital.hmpps.service
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.whenever
+import org.springframework.ldap.core.LdapTemplate
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator
+import uk.gov.justice.digital.hmpps.entity.BoroughRepository
+import uk.gov.justice.digital.hmpps.entity.CaseloadRepository
+import uk.gov.justice.digital.hmpps.entity.StaffRepository
+
+@ExtendWith(MockitoExtension::class)
+internal class StaffServiceTest {
+
+ @Mock
+ lateinit var ldapTemplate: LdapTemplate
+
+ @Mock
+ lateinit var staffRepository: StaffRepository
+
+ @Mock
+ lateinit var boroughRepository: BoroughRepository
+
+ @Mock
+ lateinit var caseloadRepository: CaseloadRepository
+
+ @InjectMocks
+ lateinit var service: StaffService
+
+ @Test
+ fun `calls caseload repository`() {
+ whenever(caseloadRepository.findByStaffCodeAndRoleCode("STCDE01", "OM")).thenReturn(
+ listOf(CaseloadGenerator.CASELOAD_ROLE_OM_1)
+ )
+ val res = service.getManagedOffenders(staffCode = "STCDE01")
+ assertThat(res[0].crn, equalTo("crn0001"))
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/TeamServiceTest.kt b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/TeamServiceTest.kt
new file mode 100644
index 0000000000..b7dfb80144
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/src/test/kotlin/uk/gov/justice/digital/hmpps/service/TeamServiceTest.kt
@@ -0,0 +1,31 @@
+package uk.gov.justice.digital.hmpps.service
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.whenever
+import uk.gov.justice.digital.hmpps.data.generator.CaseloadGenerator
+import uk.gov.justice.digital.hmpps.entity.CaseloadRepository
+
+@ExtendWith(MockitoExtension::class)
+internal class TeamServiceTest {
+
+ @Mock
+ lateinit var caseloadRepository: CaseloadRepository
+
+ @InjectMocks
+ lateinit var service: TeamService
+
+ @Test
+ fun `calls caseload repository`() {
+ whenever(caseloadRepository.findByTeamCodeAndRoleCodeOrderByAllocationDateDesc("N01BDT", "OM")).thenReturn(
+ listOf(CaseloadGenerator.CASELOAD_ROLE_OM_2)
+ )
+ val res = service.getManagedOffendersByTeam("N01BDT")
+ assertThat(res[0].crn, equalTo("crn0022"))
+ }
+}
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/.gitignore b/projects/assess-for-early-release-and-delius/tech-docs/.gitignore
new file mode 100644
index 0000000000..80d5de85a7
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/.gitignore
@@ -0,0 +1,20 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+#
+# If you find yourself ignoring temporary files generated by your text editor
+# or operating system, you probably want to add a global ignore instead:
+# git config --global core.excludesfile ~/.gitignore_global
+
+# Ignore bundler config
+/.bundle
+
+# Ignore the build directory
+/build
+
+# Ignore cache
+/.sass-cache
+/.cache
+
+# Ignore .DS_store file
+.DS_Store
+
+Staticfile.auth
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/.template_version b/projects/assess-for-early-release-and-delius/tech-docs/.template_version
new file mode 100644
index 0000000000..57ff8862e1
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/.template_version
@@ -0,0 +1,3 @@
+---
+:remote: https://github.com/alphagov/tech-docs-template.git
+:revision: b37e894
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/Gemfile b/projects/assess-for-early-release-and-delius/tech-docs/Gemfile
new file mode 100644
index 0000000000..afef363c09
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/Gemfile
@@ -0,0 +1,12 @@
+# If you do not have OpenSSL installed, change
+# the following line to use 'http://'
+source 'https://rubygems.org'
+
+# For faster file watcher updates on Windows:
+gem 'wdm', '~> 0.1.0', platforms: [:mswin, :mingw, :x64_mingw]
+
+# Windows does not come with time zone data
+gem 'tzinfo-data', platforms: [:mswin, :mingw, :x64_mingw, :jruby]
+
+# Include the tech docs gem
+gem 'govuk_tech_docs'
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/Gemfile.lock b/projects/assess-for-early-release-and-delius/tech-docs/Gemfile.lock
new file mode 100644
index 0000000000..9fdb19f88e
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/Gemfile.lock
@@ -0,0 +1,177 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ activesupport (7.0.7.2)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 1.6, < 2)
+ minitest (>= 5.1)
+ tzinfo (~> 2.0)
+ addressable (2.8.4)
+ public_suffix (>= 2.0.2, < 6.0)
+ autoprefixer-rails (10.4.13.0)
+ execjs (~> 2)
+ backports (3.24.1)
+ chronic (0.10.2)
+ chunky_png (1.4.0)
+ coffee-script (2.4.1)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.12.2)
+ commonmarker (0.23.10)
+ compass (1.0.3)
+ chunky_png (~> 1.2)
+ compass-core (~> 1.0.2)
+ compass-import-once (~> 1.0.5)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
+ sass (>= 3.3.13, < 3.5)
+ compass-core (1.0.3)
+ multi_json (~> 1.0)
+ sass (>= 3.3.0, < 3.5)
+ compass-import-once (1.0.5)
+ sass (>= 3.2, < 3.5)
+ concurrent-ruby (1.2.2)
+ contracts (0.17)
+ dotenv (2.8.1)
+ em-websocket (0.5.3)
+ eventmachine (>= 0.12.9)
+ http_parser.rb (~> 0)
+ erubis (2.7.0)
+ eventmachine (1.2.7)
+ execjs (2.8.1)
+ fast_blank (1.0.1)
+ fastimage (2.2.6)
+ ffi (1.15.5)
+ govuk_tech_docs (3.3.1)
+ autoprefixer-rails (~> 10.2)
+ chronic (~> 0.10.2)
+ middleman (~> 4.0)
+ middleman-autoprefixer (~> 2.10.0)
+ middleman-compass (>= 4.0.0)
+ middleman-livereload
+ middleman-search-gds
+ middleman-sprockets (~> 4.0.0)
+ middleman-syntax (~> 3.2.0)
+ nokogiri
+ openapi3_parser (~> 0.9.0)
+ redcarpet (~> 3.5.1)
+ haml (5.2.2)
+ temple (>= 0.8.0)
+ tilt
+ hamster (3.0.0)
+ concurrent-ruby (~> 1.0)
+ hashie (3.6.0)
+ http_parser.rb (0.8.0)
+ i18n (1.6.0)
+ concurrent-ruby (~> 1.0)
+ kramdown (2.4.0)
+ rexml
+ listen (3.8.0)
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
+ memoist (0.16.2)
+ middleman (4.4.3)
+ coffee-script (~> 2.2)
+ haml (>= 4.0.5, < 6.0)
+ kramdown (>= 2.3.0)
+ middleman-cli (= 4.4.3)
+ middleman-core (= 4.4.3)
+ middleman-autoprefixer (2.10.0)
+ autoprefixer-rails (>= 9.1.4)
+ middleman-core (>= 3.3.3)
+ middleman-cli (4.4.3)
+ thor (>= 0.17.0, < 2.0)
+ middleman-compass (4.0.1)
+ compass (>= 1.0.0, < 2.0.0)
+ middleman-core (>= 4.0.0)
+ middleman-core (4.4.3)
+ activesupport (>= 6.1, < 7.1)
+ addressable (~> 2.4)
+ backports (~> 3.6)
+ bundler (~> 2.0)
+ contracts (~> 0.13)
+ dotenv
+ erubis
+ execjs (~> 2.0)
+ fast_blank
+ fastimage (~> 2.0)
+ hamster (~> 3.0)
+ hashie (~> 3.4)
+ i18n (~> 1.6.0)
+ listen (~> 3.0)
+ memoist (~> 0.14)
+ padrino-helpers (~> 0.15.0)
+ parallel
+ rack (>= 1.4.5, < 3)
+ sassc (~> 2.0)
+ servolux
+ tilt (~> 2.0.9)
+ toml
+ uglifier (~> 3.0)
+ webrick
+ middleman-livereload (3.4.7)
+ em-websocket (~> 0.5.1)
+ middleman-core (>= 3.3)
+ rack-livereload (~> 0.3.15)
+ middleman-search-gds (0.11.2)
+ execjs (~> 2.6)
+ middleman-core (>= 3.2)
+ nokogiri (~> 1.6)
+ middleman-sprockets (4.0.0)
+ middleman-core (~> 4.0)
+ sprockets (>= 3.0)
+ middleman-syntax (3.2.0)
+ middleman-core (>= 3.2)
+ rouge (~> 3.2)
+ minitest (5.18.0)
+ multi_json (1.15.0)
+ nokogiri (1.16.5-x86_64-linux)
+ racc (~> 1.4)
+ openapi3_parser (0.9.2)
+ commonmarker (~> 0.17)
+ padrino-helpers (0.15.3)
+ i18n (>= 0.6.7, < 2)
+ padrino-support (= 0.15.3)
+ tilt (>= 1.4.1, < 3)
+ padrino-support (0.15.3)
+ parallel (1.22.1)
+ parslet (2.0.0)
+ public_suffix (5.0.1)
+ racc (1.7.3)
+ rack (2.2.8.1)
+ rack-livereload (0.3.17)
+ rack
+ rb-fsevent (0.11.2)
+ rb-inotify (0.10.1)
+ ffi (~> 1.0)
+ redcarpet (3.5.1)
+ rexml (3.3.9)
+ rouge (3.30.0)
+ sass (3.4.25)
+ sassc (2.4.0)
+ ffi (~> 1.9)
+ servolux (0.13.0)
+ sprockets (4.2.0)
+ concurrent-ruby (~> 1.0)
+ rack (>= 2.2.4, < 4)
+ temple (0.10.0)
+ thor (1.2.1)
+ tilt (2.0.11)
+ toml (0.3.0)
+ parslet (>= 1.8.0, < 3.0.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ uglifier (3.2.0)
+ execjs (>= 0.3.0, < 3)
+ webrick (1.8.2)
+
+PLATFORMS
+ x86_64-linux
+
+DEPENDENCIES
+ govuk_tech_docs
+ tzinfo-data
+ wdm (~> 0.1.0)
+
+BUNDLED WITH
+ 2.3.26
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/config.rb b/projects/assess-for-early-release-and-delius/tech-docs/config.rb
new file mode 100644
index 0000000000..76c77d53dd
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/config.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require "govuk_tech_docs"
+
+GovukTechDocs.configure(self)
+
+activate :relative_assets
+set :relative_links, true
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/config/tech-docs.yml b/projects/assess-for-early-release-and-delius/tech-docs/config/tech-docs.yml
new file mode 100644
index 0000000000..646e660be3
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/config/tech-docs.yml
@@ -0,0 +1,44 @@
+# Host to use for canonical URL generation (without trailing slash)
+host: https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/assess-for-early-release-and-delius
+
+# Header-related options
+service_name: HMPPS Assess For Early Release And Delius
+service_link: https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/assess-for-early-release-and-delius
+
+# Links to show on right-hand-side of header
+header_links:
+ Home: https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs
+ GitHub: https://github.com/ministryofjustice/hmpps-probation-integration-services#readme
+ Slack: https://mojdt.slack.com/archives/C02HQ4M2YQN # #probation-integration-tech channel
+
+# Enables search functionality. This indexes pages only and is not recommended for single-page sites.
+enable_search: true
+
+# Tracking ID from Google Analytics (e.g. UA-XXXX-Y)
+ga_tracking_id:
+
+# Enable multipage navigation in the sidebar
+multipage_nav: true
+
+# Enable collapsible navigation in the sidebar
+collapsible_nav: true
+
+# Table of contents depth – how many levels to include in the table of contents.
+# If your ToC is too long, reduce this number and we'll only show higher-level
+# headings.
+max_toc_heading_level: 2
+
+# Prevent robots from indexing (e.g. whilst in development)
+prevent_indexing: false
+
+# Contribution
+show_contribution_banner: true
+github_repo: ministryofjustice/hmpps-probation-integration-services
+github_branch: main
+
+# Slack
+owner_slack_workspace: mojdt
+default_owner_slack: '#probation-integration-tech'
+
+# OpenAPI
+api_path: https://assess-for-early-release-and-delius-dev.hmpps.service.justice.gov.uk/v3/api-docs.yaml
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/source/api-reference.html.md.erb b/projects/assess-for-early-release-and-delius/tech-docs/source/api-reference.html.md.erb
new file mode 100644
index 0000000000..a21752cfae
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/source/api-reference.html.md.erb
@@ -0,0 +1,19 @@
+---
+title: API Reference
+source_url: 'https://github.com/ministryofjustice/hmpps-probation-integration-services/blob/main/projects/assess-for-early-release-and-delius/tech-docs/source/api-reference.html.md.erb'
+weight: 20
+---
+
+
+
+
+The following documentation is also available in these formats:
+
+* [OpenAPI JSON](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/assess-for-early-release-and-delius/api-docs.json)
+* [OpenAPI YAML](https://ministryofjustice.github.io/hmpps-probation-integration-services/tech-docs/projects/assess-for-early-release-and-delius/api-docs.yaml)
+* [Swagger UI](https://assess-for-early-release-and-delius-dev.hmpps.service.justice.gov.uk/swagger-ui/index.html)
+
+api>
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/source/index.html.md.erb b/projects/assess-for-early-release-and-delius/tech-docs/source/index.html.md.erb
new file mode 100644
index 0000000000..52d19debb8
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/source/index.html.md.erb
@@ -0,0 +1,7 @@
+---
+title: About
+source_url: 'https://github.com/ministryofjustice/hmpps-probation-integration-services/blob/main/projects/assess-for-early-release-and-delius/tech-docs/source/index.html.md.erb'
+weight: 10
+---
+
+<%= URI.open('https://raw.githubusercontent.com/ministryofjustice/hmpps-probation-integration-services/main/projects/assess-for-early-release-and-delius/README.md').read.gsub(/tech-docs\/source\//, "./") %>
\ No newline at end of file
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/source/javascripts/application.js b/projects/assess-for-early-release-and-delius/tech-docs/source/javascripts/application.js
new file mode 100644
index 0000000000..8a5d80b842
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/source/javascripts/application.js
@@ -0,0 +1 @@
+//= require govuk_tech_docs
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/print.css.scss b/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/print.css.scss
new file mode 100644
index 0000000000..82b181c017
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/print.css.scss
@@ -0,0 +1,3 @@
+$is-print: true;
+
+@import "govuk_tech_docs";
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/screen-old-ie.css.scss b/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/screen-old-ie.css.scss
new file mode 100644
index 0000000000..da90cca5b0
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/screen-old-ie.css.scss
@@ -0,0 +1,4 @@
+$is-ie: true;
+$ie-version: 8;
+
+@import "govuk_tech_docs";
diff --git a/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/screen.css.scss b/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/screen.css.scss
new file mode 100644
index 0000000000..f0456338fd
--- /dev/null
+++ b/projects/assess-for-early-release-and-delius/tech-docs/source/stylesheets/screen.css.scss
@@ -0,0 +1 @@
+@import "govuk_tech_docs";
diff --git a/projects/prison-custody-status-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt b/projects/prison-custody-status-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt
index 2ccba192f4..eff2585ee6 100644
--- a/projects/prison-custody-status-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt
+++ b/projects/prison-custody-status-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt
@@ -168,7 +168,7 @@ fun Booking.prisonerMovement(movement: Movement): PrisonerMovement {
Booking.InOutStatus.IN -> PrisonerMovement.Received(
personReference,
movement.fromAgency,
- movement.toAgency!!,
+ movement.toAgency ?: throw IgnorableMessageException("TemporaryAbsenceNoAgency"),
PrisonerMovement.Type.valueOf(reason),
movement.movementReason,
dateTime
@@ -176,7 +176,7 @@ fun Booking.prisonerMovement(movement: Movement): PrisonerMovement {
Booking.InOutStatus.OUT -> PrisonerMovement.Released(
personReference,
- movement.fromAgency!!,
+ movement.fromAgency ?: throw IgnorableMessageException("TemporaryAbsenceNoAgency"),
movement.toAgency,
PrisonerMovement.Type.valueOf(reason),
movement.movementReason,
diff --git a/settings.gradle.kts b/settings.gradle.kts
index e897a719f8..c73ea07547 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,6 +1,7 @@
rootProject.name = "probation-integration-services"
include(
// ⌄ add new projects here
+ "assess-for-early-release-and-delius",
"justice-email-and-delius",
"appointment-reminders-and-delius",
"ims-and-delius",
diff --git a/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties b/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties
index 82dd18b204..eb1a55be0e 100644
--- a/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties
+++ b/tools/ingress-testing/api-client/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=57dafb5c2622c6cc08b993c85b7c06956a2f53536432a30ead46166dbca0f1e9
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
+distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME