Skip to content

Commit

Permalink
Merge pull request #2811 from ministryofjustice/feature/CAS-1205-add-…
Browse files Browse the repository at this point in the history
…filters-to-bedsearch

CAS-1205: Update Bed Search Filters
  • Loading branch information
daveawc authored Jan 17, 2025
2 parents 28c78db + a2c848a commit 2ae606e
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 209 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package uk.gov.justice.digital.hmpps.approvedpremisesapi.controller
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.BedsApiDelegate
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.BedSearchParameters
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.BedSearchResults
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TemporaryAccommodationBedSearchParameters
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.UserService
Expand All @@ -16,19 +15,13 @@ class BedSearchController(
private val cas3BedspaceSearchService: Cas3BedspaceSearchService,
private val bedSearchResultTransformer: BedSearchResultTransformer,
) : BedsApiDelegate {
override fun bedsSearchPost(bedSearchParameters: BedSearchParameters): ResponseEntity<BedSearchResults> {
override fun bedsSearchPost(temporaryAccommodationBedSearchParameters: TemporaryAccommodationBedSearchParameters): ResponseEntity<BedSearchResults> {
val user = userService.getUserForRequest()

val searchResult = when (bedSearchParameters) {
is TemporaryAccommodationBedSearchParameters -> cas3BedspaceSearchService.findBedspaces(
user = user,
probationDeliveryUnits = bedSearchParameters.probationDeliveryUnits,
startDate = bedSearchParameters.startDate,
durationInDays = bedSearchParameters.durationDays,
propertyBedAttributes = bedSearchParameters.attributes,
)
else -> throw RuntimeException("Unsupported BedSearchParameters type: ${bedSearchParameters::class.qualifiedName}")
}
val searchResult = cas3BedspaceSearchService.findBedspaces(
user = user,
temporaryAccommodationBedSearchParameters,
)

return ResponseEntity.ok(
bedSearchResultTransformer.transformDomainToApi(extractEntityFromCasResult(searchResult)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.BedSearchAttributes
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.PersonType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ServiceName
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TemporaryAccommodationBedSearchParameters
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.BookingRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.OverlapBookingsSearchResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ProbationDeliveryUnitRepository
Expand Down Expand Up @@ -41,23 +42,20 @@ class Cas3BedspaceSearchService(
@Suppress("detekt:CyclomaticComplexMethod")
fun findBedspaces(
user: UserEntity,
probationDeliveryUnits: List<UUID>,
startDate: LocalDate,
durationInDays: Int,
propertyBedAttributes: List<BedSearchAttributes>?,
searchParams: TemporaryAccommodationBedSearchParameters,
): CasResult<List<Cas3BedSearchResult>> = validatedCasResult {
val probationDeliveryUnitIds = mutableListOf<UUID>()

if (durationInDays < 1) {
if (searchParams.durationDays < 1) {
"$.durationDays" hasValidationError "mustBeAtLeast1"
}

if (probationDeliveryUnits.isEmpty()) {
if (searchParams.probationDeliveryUnits.isEmpty()) {
"$.probationDeliveryUnits" hasValidationError "empty"
} else if (probationDeliveryUnits.size > MAX_NUMBER_PDUS) {
} else if (searchParams.probationDeliveryUnits.size > MAX_NUMBER_PDUS) {
"$.probationDeliveryUnits" hasValidationError "maxNumberProbationDeliveryUnits"
} else {
probationDeliveryUnits.mapIndexed { index, id ->
searchParams.probationDeliveryUnits.mapIndexed { index, id ->
val probationDeliveryUnitEntityExist = probationDeliveryUnitRepository.existsById(id)
if (!probationDeliveryUnitEntityExist) {
"$.probationDeliveryUnits[$index]" hasValidationError "doesNotExist"
Expand All @@ -71,53 +69,56 @@ class Cas3BedspaceSearchService(
return fieldValidationError
}

val premisesCharacteristicsPropertyNames = propertyBedAttributes?.map {
when (it) {
BedSearchAttributes.SINGLE_OCCUPANCY -> BedSearchAttributes.SINGLE_OCCUPANCY.value
BedSearchAttributes.SHARED_PROPERTY -> BedSearchAttributes.SHARED_PROPERTY.value
else -> ""
val endDate = searchParams.calculateEndDate()

val candidateResults = if (searchParams.premisesFilters != null || searchParams.bedspaceFilters != null) {
return CasResult.GeneralValidationError("Filters not implemented")
} else {
val premisesCharacteristicsPropertyNames = searchParams.attributes?.map {
when (it) {
BedSearchAttributes.SINGLE_OCCUPANCY, BedSearchAttributes.SHARED_PROPERTY -> it.value
else -> ""
}
}
}

val premisesCharacteristicIds = getCharacteristicsIds(premisesCharacteristicsPropertyNames, "premises")
val premisesCharacteristicIds = getCharacteristicsIds(premisesCharacteristicsPropertyNames, "premises")

val roomCharacteristicsPropertyNames = propertyBedAttributes?.map {
when (it) {
BedSearchAttributes.WHEELCHAIR_ACCESSIBLE -> BedSearchAttributes.WHEELCHAIR_ACCESSIBLE.value
else -> ""
val roomCharacteristicsPropertyNames = searchParams.attributes?.map {
when (it) {
BedSearchAttributes.WHEELCHAIR_ACCESSIBLE -> it.value
else -> ""
}
}
}

val roomCharacteristicIds = getCharacteristicsIds(roomCharacteristicsPropertyNames, "room")

val endDate = startDate.plusDays(durationInDays.toLong() - 1)
val roomCharacteristicIds = getCharacteristicsIds(roomCharacteristicsPropertyNames, "room")

val candidateResults = bedSearchRepository.findTemporaryAccommodationBeds(
probationDeliveryUnits = probationDeliveryUnitIds,
startDate = startDate,
endDate = endDate,
probationRegionId = user.probationRegion.id,
premisesCharacteristicIds,
roomCharacteristicIds,
)
bedSearchRepository.findTemporaryAccommodationBeds(
probationDeliveryUnits = probationDeliveryUnitIds,
startDate = searchParams.startDate,
endDate = endDate,
probationRegionId = user.probationRegion.id,
premisesCharacteristicIds,
roomCharacteristicIds,
)
}

val bedIds = candidateResults.map { it.bedId }
val bedsWithABookingInTurnaround = bookingRepository.findClosestBookingBeforeDateForBeds(startDate, bedIds)
.filter { workingDayService.addWorkingDays(it.departureDate, it.turnaround?.workingDayCount ?: 0) >= startDate }
val bedsWithABookingInTurnaround = bookingRepository.findClosestBookingBeforeDateForBeds(searchParams.startDate, bedIds)
.filter { workingDayService.addWorkingDays(it.departureDate, it.turnaround?.workingDayCount ?: 0) >= searchParams.startDate }
.map { it.bed!!.id }

val results = candidateResults.filter { !bedsWithABookingInTurnaround.contains(it.bedId) }

val distinctIds = results.map { it.premisesId }.distinct()
val overlappedBookings = bookingRepository.findAllNotCancelledByPremisesIdsAndOverlappingDate(distinctIds, startDate, endDate)
val overlappedBookings = bookingRepository.findAllNotCancelledByPremisesIdsAndOverlappingDate(distinctIds, searchParams.startDate, endDate)
val crns = overlappedBookings.map { it.crn }.distinct().toSet()
val offenderSummaries = offenderService.getPersonSummaryInfoResults(
crns = crns.toSet(),
limitedAccessStrategy = user.cas3LimitedAccessStrategy(),
)

val groupedOverlappedBookings = overlappedBookings
.map { transformBookingToOverlap(it, startDate, endDate, offenderSummaries.forCrn(it.crn)) }
.map { transformBookingToOverlap(it, searchParams.startDate, endDate, offenderSummaries.forCrn(it.crn)) }
.groupBy { it.premisesId }

results.forEach {
Expand Down Expand Up @@ -167,4 +168,10 @@ class Cas3BedspaceSearchService(
}.map { it.id }.toList()
}
}

private fun TemporaryAccommodationBedSearchParameters.calculateEndDate(): LocalDate {
// Adjust to include the start date in the duration, e.g. 1st January for 1 day should end on the 1st January
val adjustedDuration = durationDays - 1
return startDate.plusDays(adjustedDuration)
}
}
70 changes: 44 additions & 26 deletions src/main/resources/static/_shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4121,44 +4121,62 @@ components:
- rfap
- mhapStJosephs
- mhapElliottHouse
BedSearchParameters:
TemporaryAccommodationBedSearchParameters:
type: object
properties:
serviceName:
type: string
startDate:
type: string
format: date
description: The date the Bed will need to be free from
durationDays:
type: integer
type: long
description: The number of days the Bed will need to be free from the start_date until
probationDeliveryUnits:
type: array
description: The list of pdus Ids to search within
items:
type: string
format: uuid
premisesFilters:
$ref: '#/components/schemas/PremisesFilters'
bedspaceFilters:
$ref: '#/components/schemas/BedspaceFilters'
attributes:
type: array
description: Bedspace and property attributes to filter on
items:
$ref: "_shared.yml#/components/schemas/BedSearchAttributes"
required:
- serviceName
- probationDeliveryUnits
- startDate
- durationDays
discriminator:
propertyName: serviceName
mapping:
temporary-accommodation: '#/components/schemas/TemporaryAccommodationBedSearchParameters'
TemporaryAccommodationBedSearchParameters:
allOf:
- $ref: '#/components/schemas/BedSearchParameters'
- type: object
properties:
probationDeliveryUnits:
type: array
description: The list of pdus Ids to search within
items:
type: string
format: uuid
attributes:
type: array
description: Bedspace and property attributes to filter on
items:
$ref: "_shared.yml#/components/schemas/BedSearchAttributes"
required:
- probationDeliveryUnits
PremisesFilters:
type: object
properties:
includedCharacteristicIds:
type: array
items:
type: string
format: uuid
excludedCharacteristicIds:
type: array
items:
type: string
format: uuid
BedspaceFilters:
type: object
properties:
includedCharacteristicIds:
type: array
items:
type: string
format: uuid
excludedCharacteristicIds:
type: array
items:
type: string
format: uuid
BedSearchResults:
type: object
properties:
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/static/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2246,7 +2246,7 @@ paths:
content:
'application/json':
schema:
$ref: '_shared.yml#/components/schemas/BedSearchParameters'
$ref: '_shared.yml#/components/schemas/TemporaryAccommodationBedSearchParameters'
required: true
responses:
200:
Expand Down
72 changes: 45 additions & 27 deletions src/main/resources/static/codegen/built-api-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2248,7 +2248,7 @@ paths:
content:
'application/json':
schema:
$ref: '#/components/schemas/BedSearchParameters'
$ref: '#/components/schemas/TemporaryAccommodationBedSearchParameters'
required: true
responses:
200:
Expand Down Expand Up @@ -8422,44 +8422,62 @@ components:
- rfap
- mhapStJosephs
- mhapElliottHouse
BedSearchParameters:
TemporaryAccommodationBedSearchParameters:
type: object
properties:
serviceName:
type: string
startDate:
type: string
format: date
description: The date the Bed will need to be free from
durationDays:
type: integer
type: long
description: The number of days the Bed will need to be free from the start_date until
probationDeliveryUnits:
type: array
description: The list of pdus Ids to search within
items:
type: string
format: uuid
premisesFilters:
$ref: '#/components/schemas/PremisesFilters'
bedspaceFilters:
$ref: '#/components/schemas/BedspaceFilters'
attributes:
type: array
description: Bedspace and property attributes to filter on
items:
$ref: "#/components/schemas/BedSearchAttributes"
required:
- serviceName
- probationDeliveryUnits
- startDate
- durationDays
discriminator:
propertyName: serviceName
mapping:
temporary-accommodation: '#/components/schemas/TemporaryAccommodationBedSearchParameters'
TemporaryAccommodationBedSearchParameters:
allOf:
- $ref: '#/components/schemas/BedSearchParameters'
- type: object
properties:
probationDeliveryUnits:
type: array
description: The list of pdus Ids to search within
items:
type: string
format: uuid
attributes:
type: array
description: Bedspace and property attributes to filter on
items:
$ref: "#/components/schemas/BedSearchAttributes"
required:
- probationDeliveryUnits
PremisesFilters:
type: object
properties:
includedCharacteristicIds:
type: array
items:
type: string
format: uuid
excludedCharacteristicIds:
type: array
items:
type: string
format: uuid
BedspaceFilters:
type: object
properties:
includedCharacteristicIds:
type: array
items:
type: string
format: uuid
excludedCharacteristicIds:
type: array
items:
type: string
format: uuid
BedSearchResults:
type: object
properties:
Expand Down
Loading

0 comments on commit 2ae606e

Please sign in to comment.