diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1c03267311..5c2740cce4 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -45,7 +45,7 @@ jobs: VALIDATE_XML: true VALIDATE_YAML: true LINTER_RULES_PATH: / - FILTER_REGEX_EXCLUDE: .+templates/.+yml # yamlint doesn't like Helm templating + FILTER_REGEX_EXCLUDE: .*templates/.*.ya?ml GITHUB_ACTIONS_CONFIG_FILE: .github/actionlint.yml GITHUB_ACTIONS_COMMAND_ARGS: -ignore=SC.+:info:.+ GITHUB_TOKEN: ${{ github.token }} \ No newline at end of file diff --git a/projects/person-search-index-from-delius/deploy/templates/contact-reindex-cronjob.yml b/projects/person-search-index-from-delius/deploy/templates/contact-reindex-cronjob.yml index 7baaced864..d34aa84a34 100644 --- a/projects/person-search-index-from-delius/deploy/templates/contact-reindex-cronjob.yml +++ b/projects/person-search-index-from-delius/deploy/templates/contact-reindex-cronjob.yml @@ -12,7 +12,15 @@ spec: serviceAccountName: person-search-index-from-delius containers: - name: contact-reindex - image: ghcr.io/ministryofjustice/hmpps-probation-integration-services/person-search-index-from-delius:{{ .Values.version }} + image: "ghcr.io/ministryofjustice/hmpps-probation-integration-services/person-search-index-from-delius:{{ .Values.version }}" + securityContext: + capabilities: + drop: + - ALL + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault resources: requests: memory: 2Gi diff --git a/projects/person-search-index-from-delius/deploy/templates/person-reindex-cronjob.yml b/projects/person-search-index-from-delius/deploy/templates/person-reindex-cronjob.yml index 93ef29e10b..aeeb5a447d 100644 --- a/projects/person-search-index-from-delius/deploy/templates/person-reindex-cronjob.yml +++ b/projects/person-search-index-from-delius/deploy/templates/person-reindex-cronjob.yml @@ -12,7 +12,15 @@ spec: serviceAccountName: person-search-index-from-delius containers: - name: person-reindex - image: ghcr.io/ministryofjustice/hmpps-probation-integration-services/person-search-index-from-delius:{{ .Values.version }} + image: "ghcr.io/ministryofjustice/hmpps-probation-integration-services/person-search-index-from-delius:{{ .Values.version }}" + securityContext: + capabilities: + drop: + - ALL + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault resources: requests: memory: 2Gi diff --git a/projects/prison-identifier-and-delius/deploy/templates/update-prison-identifiers.yaml b/projects/prison-identifier-and-delius/deploy/templates/update-prison-identifiers.yaml index 0de0578198..334b847314 100644 --- a/projects/prison-identifier-and-delius/deploy/templates/update-prison-identifiers.yaml +++ b/projects/prison-identifier-and-delius/deploy/templates/update-prison-identifiers.yaml @@ -16,6 +16,14 @@ spec: containers: - name: update-prison-identifiers image: "ghcr.io/ministryofjustice/hmpps-probation-integration-services/prison-identifier-and-delius:{{ .Values.version }}" + securityContext: + capabilities: + drop: + - ALL + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault resources: requests: memory: "1Gi" diff --git a/projects/redrive-dead-letter-queues/deploy/templates/cronjob.yml b/projects/redrive-dead-letter-queues/deploy/templates/cronjob.yml index a094438bc0..55cfacfbd7 100644 --- a/projects/redrive-dead-letter-queues/deploy/templates/cronjob.yml +++ b/projects/redrive-dead-letter-queues/deploy/templates/cronjob.yml @@ -13,6 +13,14 @@ spec: containers: - name: dlq-redrive image: "ghcr.io/ministryofjustice/hmpps-probation-integration-services/redrive-dead-letter-queues:{{ .Values.version }}" + securityContext: + capabilities: + drop: + - ALL + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault resources: requests: memory: 100Mi diff --git a/projects/workforce-allocations-to-delius/build.gradle.kts b/projects/workforce-allocations-to-delius/build.gradle.kts index fafae35ec0..f0c494bd17 100644 --- a/projects/workforce-allocations-to-delius/build.gradle.kts +++ b/projects/workforce-allocations-to-delius/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") implementation(libs.springdoc) implementation(libs.opentelemetry.annotations) diff --git a/projects/workforce-allocations-to-delius/deploy/templates/initial-allocations-report.yaml b/projects/workforce-allocations-to-delius/deploy/templates/initial-allocations-report.yaml new file mode 100644 index 0000000000..fb7ef82762 --- /dev/null +++ b/projects/workforce-allocations-to-delius/deploy/templates/initial-allocations-report.yaml @@ -0,0 +1,85 @@ +{{- $config := index .Values "initial-allocations-report" | default dict -}} +{{- if $config.enabled | default false -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: initial-allocations-report-script +data: + script.sh: | + #!/bin/bash + set -euo pipefail + date=$(date +%Y-%m-%d) + filename=initial-allocations-$date.csv + + echo Getting HMPPS Auth token... + hmpps_auth_token=$(curl -fsSL -XPOST -u "$CLIENT_ID:$CLIENT_SECRET" '{{ index .Values "generic-service" "env" "SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI" }}?grant_type=client_credentials' | jq -r .access_token) + echo Downloading report... + curl -fsSL -H "Authorization: Bearer $hmpps_auth_token" https://{{ index .Values "generic-service" "ingress" "host" }}/initial-allocations.csv > "/tmp/$filename" + echo Downloaded report with $(wc -l < "/tmp/$filename") rows + + echo Starting file upload... + file_details=$(curl -fsSL -XPOST -F "token=$SLACK_TOKEN" -F "filename=$filename" -F 'snippet_type=csv' -F "length=$(wc -c < "/tmp/$filename")" https://slack.com/api/files.getUploadURLExternal) + echo Got file upload details: "$file_details" + file_id=$(echo "$file_details" | jq -r .file_id) + upload_url=$(echo "$file_details" | jq -r .upload_url) + echo Uploading file... + curl -fsSL -F "file=@/tmp/$filename" "$upload_url" + echo Sending message... + curl -fsSL -F "token=$SLACK_TOKEN" -F "initial_comment=Initial Allocations Report ($(date '+%d/%m/%Y'))" -F "files=[{\"id\":\"$file_id\"}]" -F "channel_id={{ index .Values "initial-allocations-report" "channel_id" }}" https://slack.com/api/files.completeUploadExternal + echo Uploaded report to Slack +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: initial-allocations-report +spec: + schedule: {{ index .Values "initial-allocations-report" "schedule" }} + concurrencyPolicy: Forbid + failedJobsHistoryLimit: 1 + successfulJobsHistoryLimit: 1 + jobTemplate: + spec: + template: + spec: + serviceAccountName: workforce-allocations-to-delius + volumes: + - name: script-volume + configMap: + name: initial-allocations-report-script + containers: + - name: generate-report + image: "ghcr.io/ministryofjustice/hmpps-devops-tools:latest" + command: [ "bash", "/script.sh" ] + volumeMounts: + - name: script-volume + mountPath: /script.sh + subPath: script.sh + env: + - name: CLIENT_ID + valueFrom: + secretKeyRef: + name: workforce-allocations-to-delius-client-credentials + key: CLIENT_ID + optional: false + - name: CLIENT_SECRET + valueFrom: + secretKeyRef: + name: workforce-allocations-to-delius-client-credentials + key: CLIENT_SECRET + optional: false + - name: SLACK_TOKEN + valueFrom: + secretKeyRef: + name: slack-bot + key: TOKEN + optional: false + securityContext: + capabilities: + drop: + - ALL + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + restartPolicy: Never +{{- end -}} \ No newline at end of file diff --git a/projects/workforce-allocations-to-delius/deploy/values-dev.yml b/projects/workforce-allocations-to-delius/deploy/values-dev.yml index 3ffadd4a8b..27e4eaf548 100644 --- a/projects/workforce-allocations-to-delius/deploy/values-dev.yml +++ b/projects/workforce-allocations-to-delius/deploy/values-dev.yml @@ -9,7 +9,6 @@ generic-service: SENTRY_ENVIRONMENT: dev SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI: http://hmpps-auth.hmpps-auth-dev.svc.cluster.local/auth/oauth/token SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: http://hmpps-auth.hmpps-auth-dev.svc.cluster.local/auth/.well-known/jwks.json - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/issuer INTEGRATIONS_ALFRESCO_URL: https://hmpps-delius-alfresco-test.apps.live.cloud-platform.service.justice.gov.uk/alfresco/service/noms-spg/ SPRING_DATASOURCE_HIKARI_MAXIMUMPOOLSIZE: 5 diff --git a/projects/workforce-allocations-to-delius/deploy/values-preprod.yml b/projects/workforce-allocations-to-delius/deploy/values-preprod.yml index 6a936716e6..060868a141 100644 --- a/projects/workforce-allocations-to-delius/deploy/values-preprod.yml +++ b/projects/workforce-allocations-to-delius/deploy/values-preprod.yml @@ -9,7 +9,6 @@ generic-service: SENTRY_ENVIRONMENT: preprod SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI: http://hmpps-auth.hmpps-auth-preprod.svc.cluster.local/auth/oauth/token SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: http://hmpps-auth.hmpps-auth-preprod.svc.cluster.local/auth/.well-known/jwks.json - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/issuer INTEGRATIONS_ALFRESCO_URL: https://alfresco.pre-prod.delius.probation.hmpps.dsd.io/alfresco/service/noms-spg/ generic-prometheus-alerts: diff --git a/projects/workforce-allocations-to-delius/deploy/values-prod.yml b/projects/workforce-allocations-to-delius/deploy/values-prod.yml index 9f2d6fa980..0cdba7e31e 100644 --- a/projects/workforce-allocations-to-delius/deploy/values-prod.yml +++ b/projects/workforce-allocations-to-delius/deploy/values-prod.yml @@ -6,5 +6,9 @@ generic-service: SENTRY_ENVIRONMENT: prod SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_HMPPS-AUTH_TOKEN-URI: http://hmpps-auth.hmpps-auth-prod.svc.cluster.local/auth/oauth/token SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: http://hmpps-auth.hmpps-auth-prod.svc.cluster.local/auth/.well-known/jwks.json - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/issuer INTEGRATIONS_ALFRESCO_URL: https://alfresco.probation.service.justice.gov.uk/alfresco/service/noms-spg/ + +initial-allocations-report: + enabled: true + schedule: "30 7 1 * *" # The first of the month at 7:30am + channel_id: C035YK9FFK4 # topic-pi-workforce-allocation diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/AllocationsDataLoader.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/AllocationsDataLoader.kt index db99d8ea3f..c32bf735ea 100644 --- a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/AllocationsDataLoader.kt +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/AllocationsDataLoader.kt @@ -6,21 +6,10 @@ import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.stereotype.Component import uk.gov.justice.digital.hmpps.audit.repository.BusinessInteractionRepository +import uk.gov.justice.digital.hmpps.data.generator.* import uk.gov.justice.digital.hmpps.data.generator.BusinessInteractionGenerator.ADD_EVENT_ALLOCATION import uk.gov.justice.digital.hmpps.data.generator.BusinessInteractionGenerator.ADD_PERSON_ALLOCATION import uk.gov.justice.digital.hmpps.data.generator.BusinessInteractionGenerator.CREATE_COMPONENT_TRANSFER -import uk.gov.justice.digital.hmpps.data.generator.ContactTypeGenerator -import uk.gov.justice.digital.hmpps.data.generator.CourtReportTypeGenerator -import uk.gov.justice.digital.hmpps.data.generator.DatasetGenerator -import uk.gov.justice.digital.hmpps.data.generator.OffenceGenerator -import uk.gov.justice.digital.hmpps.data.generator.ProviderGenerator -import uk.gov.justice.digital.hmpps.data.generator.ReferenceDataGenerator -import uk.gov.justice.digital.hmpps.data.generator.RegisterTypeGenerator -import uk.gov.justice.digital.hmpps.data.generator.RequirementAdditionalMainCategoryGenerator -import uk.gov.justice.digital.hmpps.data.generator.RequirementMainCategoryGenerator -import uk.gov.justice.digital.hmpps.data.generator.StaffGenerator -import uk.gov.justice.digital.hmpps.data.generator.TeamGenerator -import uk.gov.justice.digital.hmpps.data.generator.UserGenerator import uk.gov.justice.digital.hmpps.data.repository.* import uk.gov.justice.digital.hmpps.integrations.delius.allocations.entity.ReferenceDataRepository import uk.gov.justice.digital.hmpps.integrations.delius.contact.ContactTypeRepository @@ -48,7 +37,8 @@ class AllocationsDataLoader( private val caseViewDataLoader: CaseViewDataLoader, private val registerTypeRepository: RegisterTypeRepository, private val limitedAccessDataLoader: LimitedAccessDataLoader, - private val registrationDataLoader: RegistrationDataLoader + private val registrationDataLoader: RegistrationDataLoader, + private val existingAllocationsDataLoader: ExistingAllocationsDataLoader, ) : ApplicationListener { @PostConstruct @@ -142,5 +132,6 @@ class AllocationsDataLoader( caseViewDataLoader.loadData() limitedAccessDataLoader.loadData() registrationDataLoader.loadData() + existingAllocationsDataLoader.loadData() } } diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/ExistingAllocationsDataLoader.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/ExistingAllocationsDataLoader.kt new file mode 100644 index 0000000000..172cdd2dee --- /dev/null +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/ExistingAllocationsDataLoader.kt @@ -0,0 +1,33 @@ +package uk.gov.justice.digital.hmpps.data + +import jakarta.persistence.EntityManager +import jakarta.transaction.Transactional +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Component +import uk.gov.justice.digital.hmpps.data.generator.* +import uk.gov.justice.digital.hmpps.integrations.delius.event.OrderManagerRepository + +@Component +@ConditionalOnProperty("seed.database") +class ExistingAllocationsDataLoader( + private val orderManagerRepository: OrderManagerRepository, + private val existingAllocationsRefDataLoader: ExistingAllocationsRefDataLoader, +) { + fun loadData() { + existingAllocationsRefDataLoader.loadData() + orderManagerRepository.save(OrderManagerGenerator.UNALLOCATED) + orderManagerRepository.save(OrderManagerGenerator.INITIAL_ALLOCATION) + } +} + +@Component +@Transactional +class ExistingAllocationsRefDataLoader(private val entityManager: EntityManager) { + fun loadData() { + entityManager.persist(ProviderGenerator.PDU) + entityManager.persist(ProviderGenerator.LAU) + entityManager.persist(TeamGenerator.TEAM_IN_LAU) + entityManager.persist(StaffGenerator.ALLOCATED) + entityManager.persist(EventGenerator.HAS_INITIAL_ALLOCATION) + } +} \ No newline at end of file diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/entity/ProbationDeliveryUnit.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/entity/ProbationDeliveryUnit.kt new file mode 100644 index 0000000000..5a21374aa6 --- /dev/null +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/entity/ProbationDeliveryUnit.kt @@ -0,0 +1,63 @@ +package uk.gov.justice.digital.hmpps.data.entity + +import jakarta.persistence.* +import org.hibernate.annotations.Immutable +import uk.gov.justice.digital.hmpps.integrations.delius.provider.StaffWithUser +import uk.gov.justice.digital.hmpps.integrations.delius.provider.Team +import java.time.ZonedDateTime + +@Entity +@Immutable +@Table(name = "borough") +class ProbationDeliveryUnit( + @Id + @Column(name = "borough_id") + val id: Long, + val code: String, + val description: String, +) + +@Entity +@Immutable +@Table(name = "district") +class LocalAdminUnit( + @Id + @Column(name = "district_id") + val id: Long, + + @ManyToOne + @JoinColumn(name = "borough_id") + val pdu: ProbationDeliveryUnit, +) + +@Entity +@Immutable +@Table(name = "team") +class TeamWithLocalAdminUnit( + @Id + @Column(name = "team_id") + val id: Long, + + @Column(name = "code", columnDefinition = "char(6)") + val code: String, + + @Column(name = "probation_area_id") + val providerId: Long, + + val description: String, + + @Column(name = "end_date") + val endDate: ZonedDateTime? = null, + + @ManyToMany(mappedBy = "teams") + val staff: List = listOf(), + + @ManyToOne + @JoinColumn(name = "district_id") + val localAdminUnit: LocalAdminUnit? = null, +) + +fun Team.withLocalAdminUnit(localAdminUnit: LocalAdminUnit) = + TeamWithLocalAdminUnit(id, code, providerId, description, endDate, staff, localAdminUnit) + +fun TeamWithLocalAdminUnit.toTeam() = Team(id, code, providerId, description, endDate, staff) \ No newline at end of file diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/EventGenerator.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/EventGenerator.kt index fe0102c80c..b2bbf913da 100644 --- a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/EventGenerator.kt +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/EventGenerator.kt @@ -9,6 +9,7 @@ object EventGenerator { val NEW = generate(eventNumber = "2") val HISTORIC = generate(eventNumber = "3") val DELETED = generate(eventNumber = "1", softDeleted = true) + val HAS_INITIAL_ALLOCATION = generate(eventNumber = "4") val INACTIVE = generate(eventNumber = "99", active = false) val CASE_VIEW = forCaseView() diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/OrderManagerGenerator.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/OrderManagerGenerator.kt index 363018b58e..bba050ac07 100644 --- a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/OrderManagerGenerator.kt +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/OrderManagerGenerator.kt @@ -1,6 +1,8 @@ package uk.gov.justice.digital.hmpps.data.generator +import uk.gov.justice.digital.hmpps.data.entity.toTeam import uk.gov.justice.digital.hmpps.data.generator.RequirementManagerGenerator.build +import uk.gov.justice.digital.hmpps.datetime.EuropeLondon import uk.gov.justice.digital.hmpps.integrations.delius.event.OrderManager import uk.gov.justice.digital.hmpps.integrations.delius.provider.Provider import uk.gov.justice.digital.hmpps.integrations.delius.provider.Staff @@ -18,6 +20,19 @@ object OrderManagerGenerator { startDateTime = ManagerGenerator.START_DATE_TIME.minusDays(2), staff = StaffGenerator.STAFF_FOR_INACTIVE_EVENT ) + var INITIAL_ALLOCATION = generate( + startDateTime = ZonedDateTime.of(2024, 5, 7, 12, 0, 0, 0, EuropeLondon), + eventId = EventGenerator.HAS_INITIAL_ALLOCATION.id, + staff = StaffGenerator.ALLOCATED, + team = TeamGenerator.TEAM_IN_LAU.toTeam() + ) + var UNALLOCATED = generate( + startDateTime = ZonedDateTime.of(2024, 5, 1, 12, 0, 0, 0, EuropeLondon), + eventId = EventGenerator.HAS_INITIAL_ALLOCATION.id, + team = TeamGenerator.TEAM_IN_LAU.toTeam() + ).also { + it.endDate = ManagerGenerator.START_DATE_TIME + } fun generate( id: Long = IdGenerator.getAndIncrement(), diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt index 9001b99d50..a716c7323e 100644 --- a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/ProviderGenerator.kt @@ -1,5 +1,7 @@ package uk.gov.justice.digital.hmpps.data.generator +import uk.gov.justice.digital.hmpps.data.entity.LocalAdminUnit +import uk.gov.justice.digital.hmpps.data.entity.ProbationDeliveryUnit import uk.gov.justice.digital.hmpps.integrations.delius.provider.Provider object ProviderGenerator { @@ -8,4 +10,6 @@ object ProviderGenerator { "N02", "NPS North East" ) + val PDU = ProbationDeliveryUnit(id = IdGenerator.getAndIncrement(), code = "PDU1", description = "Some PDU") + val LAU = LocalAdminUnit(id = IdGenerator.getAndIncrement(), pdu = PDU) } diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt index a601e6e4fb..3da9525543 100644 --- a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/StaffGenerator.kt @@ -21,6 +21,11 @@ object StaffGenerator { "John", "Smith" ) + val ALLOCATED = generateStaff( + "TEST01", + "John", + "Smith" + ) val STAFF_WITH_USER = generateStaffWithUser( "${TeamGenerator.ALLOCATION_TEAM.code}1", "Joe", diff --git a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/TeamGenerator.kt b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/TeamGenerator.kt index 79baa2e0b9..0006bd2922 100644 --- a/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/TeamGenerator.kt +++ b/projects/workforce-allocations-to-delius/src/dev/kotlin/uk/gov/justice/digital/hmpps/data/generator/TeamGenerator.kt @@ -1,5 +1,6 @@ package uk.gov.justice.digital.hmpps.data.generator +import uk.gov.justice.digital.hmpps.data.entity.withLocalAdminUnit import uk.gov.justice.digital.hmpps.integrations.delius.provider.Team import java.time.ZonedDateTime @@ -10,12 +11,13 @@ object TeamGenerator { ProviderGenerator.DEFAULT.id ) val ALLOCATION_TEAM = generate("N02ABS") + val TEAM_IN_LAU = generate("N03AAA", "Description for N03AAA").withLocalAdminUnit(ProviderGenerator.LAU) fun generate( code: String, description: String = code, providerId: Long = ProviderGenerator.DEFAULT.id, id: Long = IdGenerator.getAndIncrement(), - endDate: ZonedDateTime? = null + endDate: ZonedDateTime? = null, ) = Team(id, code, providerId, description, endDate) } diff --git a/projects/workforce-allocations-to-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/InitialAllocationIntegrationTest.kt b/projects/workforce-allocations-to-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/InitialAllocationIntegrationTest.kt new file mode 100644 index 0000000000..ec7b3755a1 --- /dev/null +++ b/projects/workforce-allocations-to-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/InitialAllocationIntegrationTest.kt @@ -0,0 +1,36 @@ +package uk.gov.justice.digital.hmpps + +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.result.MockMvcResultMatchers.content +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken + +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = RANDOM_PORT) +class InitialAllocationIntegrationTest { + @Autowired + lateinit var mockMvc: MockMvc + + @Test + fun `returns csv report`() { + mockMvc + .perform(get("/initial-allocations.csv").accept("text/csv").withToken()) + .andExpect(status().is2xxSuccessful) + .andExpect(content().contentTypeCompatibleWith("text/csv;charset=UTF-8")) + .andExpect( + content().string( + """ + crn,eventNumber,sentenceType,allocatedBy,allocationDate,endDate,officerCode,teamCode,teamDescription,providerCode,providerDescription,pduCode,pduDescription + X123456,4,None,"HMPPS Allocations",07/05/2024,,"TEST01 ",N03AAA,"Description for N03AAA",N02,"NPS North East",PDU1,"Some PDU" + + """.trimIndent() + ) + ) + } +} diff --git a/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/InitialAllocationResource.kt b/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/InitialAllocationResource.kt new file mode 100644 index 0000000000..44648505df --- /dev/null +++ b/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/resource/InitialAllocationResource.kt @@ -0,0 +1,21 @@ +package uk.gov.justice.digital.hmpps.api.resource + +import io.swagger.v3.oas.annotations.Operation +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController +import uk.gov.justice.digital.hmpps.config.CsvMapperConfig.csvMapper +import uk.gov.justice.digital.hmpps.integrations.delius.allocations.InitialAllocation +import uk.gov.justice.digital.hmpps.integrations.delius.allocations.InitialAllocationRepository + +@RestController +class InitialAllocationResource( + private val initialAllocationRepository: InitialAllocationRepository +) { + @PreAuthorize("hasRole('PROBATION_API__WORKFORCE_ALLOCATIONS__CASE_DETAIL')") + @Operation(summary = "A report of all allocations created by either the Manage a Workforce Allocation tool or Delius, since the start of 2024.") + @GetMapping("/initial-allocations.csv", produces = ["text/csv"]) + fun getInitialAllocations(): String = csvMapper + .writer(csvMapper.schemaFor(InitialAllocation::class.java).withHeader()) + .writeValueAsString(initialAllocationRepository.findAllInitialAllocations()) +} diff --git a/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/CsvMapperConfig.kt b/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/CsvMapperConfig.kt new file mode 100644 index 0000000000..55b96d6ce6 --- /dev/null +++ b/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/CsvMapperConfig.kt @@ -0,0 +1,9 @@ +package uk.gov.justice.digital.hmpps.config + +import com.fasterxml.jackson.dataformat.csv.CsvMapper +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.registerKotlinModule + +object CsvMapperConfig { + val csvMapper = CsvMapper().also { it.registerKotlinModule().registerModule(JavaTimeModule()) } +} diff --git a/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/allocations/InitialAllocation.kt b/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/allocations/InitialAllocation.kt new file mode 100644 index 0000000000..86571326cb --- /dev/null +++ b/projects/workforce-allocations-to-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/allocations/InitialAllocation.kt @@ -0,0 +1,94 @@ +package uk.gov.justice.digital.hmpps.integrations.delius.allocations + +import com.fasterxml.jackson.annotation.JsonFormat +import com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING +import com.fasterxml.jackson.annotation.JsonPropertyOrder +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.stereotype.Repository +import uk.gov.justice.digital.hmpps.integrations.delius.person.Person +import java.time.LocalDate + +@JsonPropertyOrder( + "crn", + "eventNumber", + "sentenceType", + "allocatedBy", + "allocationDate", + "endDate", + "officerCode", + "teamCode", + "teamDescription", + "providerCode", + "providerDescription" +) +interface InitialAllocation { + val crn: String + val eventNumber: Int + val sentenceType: String + val allocatedBy: String + + @get:JsonFormat(shape = STRING, pattern = "dd/MM/yyyy") + val allocationDate: LocalDate + + @get:JsonFormat(shape = STRING, pattern = "dd/MM/yyyy") + val endDate: LocalDate + val officerCode: String + val teamCode: String + val teamDescription: String + val pduCode: String + val pduDescription: String + val providerCode: String + val providerDescription: String +} + +@Repository +interface InitialAllocationRepository : JpaRepository { + @Query( + value = """ + select + allocation.crn, + allocation.event_number as "eventNumber", + allocation.sentence_type as "sentenceType", + (case when created_by.distinguished_name = 'HMPPSAllocations' then 'HMPPS Allocations' else 'Delius' end) as "allocatedBy", + allocation.allocation_date as "allocationDate", + allocation.end_date as "endDate", + staff.officer_code as "officerCode", + team.code as "teamCode", + team.description as "teamDescription", + borough.code as "pduCode", + borough.description as "pduDescription", + probation_area.code as "providerCode", + probation_area.description as "providerDescription" + from ( + select + offender.crn, + event.event_number, + (case when r_disposal_type.sentence_type is null then 'None' when r_disposal_type.sentence_type in ('NC','SC') then 'Custody' else 'Community' end) as sentence_type, + order_manager.created_by_user_id, + order_manager.allocation_date, + order_manager.end_date, + order_manager.allocation_staff_id, + order_manager.allocation_team_id, + order_manager.probation_area_id, + lag(order_manager.allocation_staff_id) over (partition by order_manager.event_id order by allocation_date) as prev_staff_id + from order_manager + join event on event.event_id = order_manager.event_id and event.soft_deleted = 0 + left join disposal on disposal.event_id = event.event_id and disposal.soft_deleted = 0 + left join r_disposal_type on r_disposal_type.disposal_type_id = disposal.disposal_type_id + join offender on offender.offender_id = event.offender_id and offender.soft_deleted = 0 + where order_manager.soft_deleted = 0 + and order_manager.allocation_date > :startDate + ) allocation + join user_ created_by on created_by.user_id = allocation.created_by_user_id + join staff on staff.staff_id = allocation.allocation_staff_id and staff.officer_code not like '%U' and upper(staff.forename) || ' ' || upper(staff.surname) not like '%AWAITING ALLOCATION%' + join staff previous_staff on previous_staff.staff_id = allocation.prev_staff_id and (previous_staff.officer_code like '%U' or upper(previous_staff.forename) || ' ' || upper(previous_staff.surname) like '%AWAITING ALLOCATION%') + join team on team.team_id = allocation.allocation_team_id + join district on district.district_id = team.district_id + join borough on borough.borough_id = district.borough_id + join probation_area on probation_area.probation_area_id = allocation.probation_area_id + """, + nativeQuery = true + ) + fun findAllInitialAllocations(startDate: LocalDate = LocalDate.ofYearDay(2024, 1)): List +}