Skip to content

Commit

Permalink
Merge pull request #2732 from ministryofjustice/APS-1672-premises-ove…
Browse files Browse the repository at this point in the history
…rbooking-summary

Implemented premises overbooking summary range.
  • Loading branch information
vbala-moj authored Dec 19, 2024
2 parents e1a221a + 9276465 commit 337f112
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package uk.gov.justice.digital.hmpps.approvedpremisesapi.service.cas1

import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1OverbookingRange
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesGender
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesRepository
import uk.gov.justice.digital.hmpps.approvedpremisesapi.results.CasResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.PremisesService
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.cas1.planning.SpacePlanningService
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.Cas1PremiseOverbookingCalculator
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.DateRange
import java.time.LocalDate
import java.util.UUID
Expand All @@ -19,23 +21,42 @@ class Cas1PremisesService(
val outOfServiceBedService: Cas1OutOfServiceBedService,
val spacePlanningService: SpacePlanningService,
) {
companion object {
private const val OVERBOOKING_RANGE_DURATION_WEEKS = 12L
}
fun getPremisesSummary(premisesId: UUID): CasResult<Cas1PremisesSummaryInfo> {
val premise = premisesRepository.findByIdOrNull(premisesId)
?: return CasResult.NotFound("premises", premisesId.toString())

val bedCount = premisesService.getBedCount(premise)
val outOfServiceBedsCount = outOfServiceBedService.getCurrentOutOfServiceBedsCountForPremisesId(premisesId)
val overbookingSummary = premise.takeIf { it.supportsSpaceBookings }?.let { buildOverBookingSummary(it) } ?: emptyList()

return CasResult.Success(
Cas1PremisesSummaryInfo(
entity = premise,
bedCount = bedCount,
availableBeds = bedCount - outOfServiceBedsCount,
outOfServiceBeds = outOfServiceBedsCount,
overbookingSummary = overbookingSummary,
),
)
}

private fun buildOverBookingSummary(premises: ApprovedPremisesEntity): List<Cas1OverbookingRange> {
val premisesCapacitySummary = spacePlanningService.capacity(
premises,
range = DateRange(LocalDate.now(), LocalDate.now().plusWeeks(OVERBOOKING_RANGE_DURATION_WEEKS)),
excludeSpaceBookingId = null,
)

val overbookedDays = premisesCapacitySummary
.byDay
.filter { it.isPremiseOverbooked() }

return Cas1PremiseOverbookingCalculator().calculate(overbookedDays)
}

fun getPremises(gender: ApprovedPremisesGender?, apAreaId: UUID?) = premisesRepository.findForSummaries(gender, apAreaId)

fun findPremiseById(id: UUID) = premisesRepository.findByIdOrNull(id)
Expand Down Expand Up @@ -67,5 +88,6 @@ class Cas1PremisesService(
val bedCount: Int,
val availableBeds: Int,
val outOfServiceBeds: Int,
val overbookingSummary: List<Cas1OverbookingRange>,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ class SpacePlanningService(
): PremiseCharacteristicAvailability {
return PremiseCharacteristicAvailability(
characteristicPropertyName = characteristicPropertyName,
availableBedsCount = availableBeds.count { it.bed.hasCharacteristic(characteristicPropertyName) },
bookingsCount = bookings.count { it.hasCharacteristic(characteristicPropertyName) },
availableBedCount = availableBeds.count { it.bed.hasCharacteristic(characteristicPropertyName) },
bookingCount = bookings.count { it.hasCharacteristic(characteristicPropertyName) },
)
}

Expand Down Expand Up @@ -165,18 +165,30 @@ class SpacePlanningService(
val range: DateRange,
val byDay: List<PremiseCapacityForDay>,
)

data class PremiseCapacityForDay(
val day: LocalDate,
val totalBedCount: Int,
val availableBedCount: Int,
val bookingCount: Int,
val characteristicAvailability: List<PremiseCharacteristicAvailability>,
)
) {
fun isPremiseOverbooked(): Boolean {
return isPremisesCapacityOverbooked() || characteristicAvailability.any { it.isCharacteristicOverbooked() }
}

private fun isPremisesCapacityOverbooked(): Boolean {
return bookingCount > availableBedCount
}
}

data class PremiseCharacteristicAvailability(
val characteristicPropertyName: String,
val availableBedsCount: Int,
val bookingsCount: Int,
)
val availableBedCount: Int,
val bookingCount: Int,
) {

fun isCharacteristicOverbooked(): Boolean {
return bookingCount > availableBedCount
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Cas1PremiseCapacitySummaryTransformer(
private fun SpacePlanningService.PremiseCharacteristicAvailability.toApiType() =
Cas1PremiseCharacteristicAvailability(
characteristic = Cas1SpaceBookingCharacteristic.entries.first { it.value == this.characteristicPropertyName },
availableBedsCount = availableBedsCount,
bookingsCount = bookingsCount,
availableBedsCount = availableBedCount,
bookingsCount = bookingCount,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Cas1PremisesTransformer(
apArea = apAreaTransformer.transformJpaToApi(entity.probationRegion.apArea!!),
supportsSpaceBookings = entity.supportsSpaceBookings,
managerDetails = entity.managerDetails,
overbookingSummary = emptyList(),
overbookingSummary = premisesSummaryInfo.overbookingSummary,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.util

import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.Cas1OverbookingRange
import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.cas1.planning.SpacePlanningService

class Cas1PremiseOverbookingCalculator {

fun calculate(overbookedDays: List<SpacePlanningService.PremiseCapacityForDay>): List<Cas1OverbookingRange> {
if (overbookedDays.isEmpty()) return emptyList()

val sortedOverBookedDays = overbookedDays.distinctBy { it.day }.sortedBy { it.day }

val overbookingRanges = mutableListOf<Cas1OverbookingRange>()
var rangeStart = sortedOverBookedDays.first().day
var previousDay = rangeStart

// Calculate consecutive overbooking ranges
for (current in sortedOverBookedDays.drop(1)) {
if (current.day != previousDay.plusDays(1)) {
overbookingRanges.add(
Cas1OverbookingRange(startInclusive = rangeStart, endInclusive = previousDay),
)
rangeStart = current.day
}
previousDay = current.day
}
// Add the final range
overbookingRanges.add(
Cas1OverbookingRange(startInclusive = rangeStart, endInclusive = previousDay),
)
return overbookingRanges
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day1Capacity.availableBedCount).isEqualTo(6)

assertThat(day1Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 1),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 1),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 1),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 1),
)

val day2Capacity = capacity.byDay[1]
Expand All @@ -244,12 +244,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day2Capacity.availableBedCount).isEqualTo(5)

assertThat(day2Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 1),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 1),
)

val day3Capacity = capacity.byDay[2]
Expand All @@ -259,12 +259,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day3Capacity.availableBedCount).isEqualTo(4)

assertThat(day3Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 1),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 1),
)

val day4Capacity = capacity.byDay[3]
Expand All @@ -274,12 +274,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day4Capacity.availableBedCount).isEqualTo(5)

assertThat(day4Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 0),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 0),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 0),
)

val day5Capacity = capacity.byDay[4]
Expand Down Expand Up @@ -310,12 +310,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day1Capacity.availableBedCount).isEqualTo(6)

assertThat(day1Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 1),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 1),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 1),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 1),
)

val day2Capacity = capacity.byDay[1]
Expand All @@ -325,12 +325,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day2Capacity.availableBedCount).isEqualTo(5)

assertThat(day2Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 1),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 1),
)

val day3Capacity = capacity.byDay[2]
Expand All @@ -340,12 +340,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day3Capacity.availableBedCount).isEqualTo(4)

assertThat(day3Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 1),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 1),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 1),
)

val day4Capacity = capacity.byDay[3]
Expand All @@ -355,12 +355,12 @@ class SpacePlanningServiceTest : InitialiseDatabasePerClassTestBase() {
assertThat(day4Capacity.availableBedCount).isEqualTo(5)

assertThat(day4Capacity.characteristicAvailability).containsExactly(
PremiseCharacteristicAvailability("isArsonSuitable", availableBedsCount = 1, bookingsCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedsCount = 1, bookingsCount = 0),
PremiseCharacteristicAvailability("isSingle", availableBedsCount = 2, bookingsCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedsCount = 0, bookingsCount = 0),
PremiseCharacteristicAvailability("isArsonSuitable", availableBedCount = 1, bookingCount = 0),
PremiseCharacteristicAvailability("hasEnSuite", availableBedCount = 1, bookingCount = 0),
PremiseCharacteristicAvailability("isSingle", availableBedCount = 2, bookingCount = 2),
PremiseCharacteristicAvailability("isStepFreeDesignated", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isSuitedForSexOffenders", availableBedCount = 0, bookingCount = 0),
PremiseCharacteristicAvailability("isWheelchairDesignated", availableBedCount = 0, bookingCount = 0),
)

val day5Capacity = capacity.byDay[4]
Expand Down
Loading

0 comments on commit 337f112

Please sign in to comment.