Skip to content

Commit 0bf2c94

Browse files
MIA-1718 typed schedules
1 parent 75d81c8 commit 0bf2c94

File tree

13 files changed

+310
-52
lines changed

13 files changed

+310
-52
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
import com.fasterxml.jackson.annotation.JsonSubTypes
5+
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
6+
import com.fasterxml.jackson.annotation.JsonTypeInfo
7+
import java.time.LocalTime
8+
9+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
10+
@JsonSubTypes(
11+
value = [
12+
Type(value = SingleSchedule::class, name = "SINGLE"),
13+
Type(value = FreeFormSchedule::class, name = "FREEFORM"),
14+
Type(value = WeeklySchedule::class, name = "WEEKLY"),
15+
Type(value = BiWeeklySchedule::class, name = "BIWEEKLY"),
16+
Type(value = ShiftSchedule::class, name = "SHIFT"),
17+
],
18+
)
19+
sealed interface AuthorisationSchedule {
20+
val type: Type
21+
22+
enum class Type {
23+
SINGLE,
24+
FREEFORM,
25+
WEEKLY,
26+
BIWEEKLY,
27+
SHIFT,
28+
}
29+
}
30+
31+
data class SingleSchedule(
32+
val startTime: LocalTime,
33+
val returnTime: LocalTime,
34+
) : AuthorisationSchedule {
35+
override val type = AuthorisationSchedule.Type.SINGLE
36+
}
37+
38+
data object FreeFormSchedule : AuthorisationSchedule {
39+
override val type = AuthorisationSchedule.Type.FREEFORM
40+
}
41+
42+
data class WeekDayPattern(val day: Int, val overnight: Boolean, val startTime: LocalTime, val returnTime: LocalTime)
43+
44+
data class WeeklySchedule(
45+
val weeklyPattern: List<WeekDayPattern> = listOf(),
46+
val absencesPerDay: Int? = null,
47+
) : AuthorisationSchedule {
48+
override val type = AuthorisationSchedule.Type.WEEKLY
49+
}
50+
51+
data class BiWeeklyPattern(val weekA: List<WeekDayPattern> = listOf(), val weekB: List<WeekDayPattern> = listOf())
52+
53+
data class BiWeeklySchedule(
54+
@JsonProperty("biweeklyPattern")
55+
val biWeeklyPattern: BiWeeklyPattern = BiWeeklyPattern(),
56+
val absencesPerDay: Int? = null,
57+
) : AuthorisationSchedule {
58+
override val type = AuthorisationSchedule.Type.BIWEEKLY
59+
}
60+
61+
data class ShiftPattern(
62+
val type: ShiftPattern.Type,
63+
val count: Int,
64+
val startTime: LocalTime?,
65+
val returnTime: LocalTime?,
66+
) {
67+
enum class Type {
68+
DAY,
69+
NIGHT,
70+
REST,
71+
}
72+
}
73+
74+
data class ShiftSchedule(
75+
val shiftPattern: List<ShiftPattern> = listOf(),
76+
) : AuthorisationSchedule {
77+
override val type = AuthorisationSchedule.Type.SHIFT
78+
}

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/domain/tap/authorisation/TemporaryAbsenceAuthorisation.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation
22

3-
import com.fasterxml.jackson.databind.JsonNode
43
import jakarta.persistence.Column
54
import jakarta.persistence.Entity
65
import jakarta.persistence.Id
@@ -106,7 +105,7 @@ class TemporaryAbsenceAuthorisation(
106105
end: LocalDate,
107106
locations: SequencedSet<Location>,
108107
reasonPath: ReasonPath,
109-
schedule: JsonNode?,
108+
schedule: AuthorisationSchedule?,
110109
legacyId: Long?,
111110
@Id
112111
@Column(name = "id", nullable = false, updatable = false)
@@ -201,7 +200,7 @@ class TemporaryAbsenceAuthorisation(
201200

202201
@JdbcTypeCode(SqlTypes.JSON)
203202
@Column(name = "schedule")
204-
var schedule: JsonNode? = schedule
203+
var schedule: AuthorisationSchedule? = schedule
205204
private set
206205

207206
@Column(name = "legacy_id")
@@ -338,8 +337,8 @@ class TemporaryAbsenceAuthorisation(
338337
}
339338
}
340339

341-
fun applySchedule(json: JsonNode) = apply {
342-
schedule = json
340+
fun applySchedule(schedule: AuthorisationSchedule) = apply {
341+
this.schedule = schedule
343342
}
344343

345344
fun applyLegacyId(legacyId: Long) = apply {

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/model/CreateTapAuthorisationRequest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.Re
1515
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataKey
1616
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataRequired
1717
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.of
18+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.AuthorisationSchedule
1819
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.referencedata.AuthorisationStatus
1920
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.referencedata.OccurrenceStatus
2021
import uk.gov.justice.digital.hmpps.externalmovementsapi.model.actions.DateRange
@@ -40,6 +41,7 @@ data class CreateTapAuthorisationRequest(
4041
override val start: LocalDate,
4142
override val end: LocalDate,
4243
val contactInformation: String?,
44+
val schedule: AuthorisationSchedule?,
4345
@JsonIgnore
4446
val submittedAt: LocalDateTime = ExternalMovementContext.get().requestAt,
4547
@JsonIgnore
@@ -48,7 +50,6 @@ data class CreateTapAuthorisationRequest(
4850
val approvedAt: LocalDateTime? = if (statusCode == AuthorisationStatus.Code.APPROVED) ExternalMovementContext.get().requestAt else null,
4951
@JsonIgnore
5052
val approvedBy: String? = if (statusCode == AuthorisationStatus.Code.APPROVED) ExternalMovementContext.get().username else null,
51-
val schedule: JsonNode? = null,
5253
) : ReferenceDataRequired,
5354
DateRange,
5455
StartAndEnd<LocalDate> {

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/model/TapAuthorisation.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package uk.gov.justice.digital.hmpps.externalmovementsapi.model
22

3-
import com.fasterxml.jackson.databind.JsonNode
43
import io.swagger.v3.oas.annotations.media.Schema
4+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.AuthorisationSchedule
55
import uk.gov.justice.digital.hmpps.externalmovementsapi.integration.prisonregister.Prison
66
import uk.gov.justice.digital.hmpps.externalmovementsapi.model.location.Location
77
import uk.gov.justice.digital.hmpps.externalmovementsapi.model.referencedata.CodedDescription
@@ -28,7 +28,7 @@ data class TapAuthorisation(
2828
val totalOccurrenceCount: Long,
2929
val occurrences: List<Occurrence>,
3030
val locations: SequencedSet<Location>,
31-
val schedule: JsonNode?,
31+
val schedule: AuthorisationSchedule?,
3232
val comments: String?,
3333
) {
3434
@Schema(name = "TapAuthorisation.Occurrence")

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/sync/internal/ResyncExtensions.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package uk.gov.justice.digital.hmpps.externalmovementsapi.sync.internal
22

3-
import com.fasterxml.jackson.databind.ObjectMapper
4-
import com.fasterxml.jackson.module.kotlin.treeToValue
53
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataDomain.Code.ABSENCE_REASON_CATEGORY
64
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.ReferenceDataPaths
5+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.SingleSchedule
76
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.TemporaryAbsenceAuthorisation
87
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.occurrence.TemporaryAbsenceOccurrence
98
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.referencedata.AuthorisationStatus.Code.APPROVED
@@ -31,7 +30,6 @@ import uk.gov.justice.digital.hmpps.externalmovementsapi.model.actions.occurrenc
3130
import uk.gov.justice.digital.hmpps.externalmovementsapi.model.location.Location
3231
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.migrate.TapAuthorisation
3332
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.migrate.TapOccurrence
34-
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.write.AuthorisationSchedule
3533
import java.time.LocalDate.now
3634

3735
internal fun TemporaryAbsenceAuthorisation.applyAbsenceCategorisation(
@@ -105,16 +103,16 @@ internal fun TemporaryAbsenceOccurrence.checkCancellation(request: TapOccurrence
105103
}
106104
}
107105

108-
fun TemporaryAbsenceAuthorisation.occurrence(objectMapper: ObjectMapper): TemporaryAbsenceOccurrence? = this.schedule?.let {
109-
val schedule = objectMapper.treeToValue<AuthorisationSchedule>(it)
106+
fun TemporaryAbsenceAuthorisation.occurrence(): TemporaryAbsenceOccurrence? = this.schedule?.takeIf { it is SingleSchedule }?.let {
107+
it as SingleSchedule
110108
TemporaryAbsenceOccurrence(
111109
authorisation = this,
112110
absenceType = absenceType,
113111
absenceSubType = absenceSubType,
114112
absenceReasonCategory = absenceReasonCategory,
115113
absenceReason = absenceReason,
116-
start = start.atTime(schedule.startTime),
117-
end = end.atTime(schedule.returnTime),
114+
start = start.atTime(it.startTime),
115+
end = end.atTime(it.returnTime),
118116
contactInformation = null,
119117
accompaniedBy = accompaniedBy,
120118
transport = transport,

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/sync/internal/ResyncTapHierarchy.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package uk.gov.justice.digital.hmpps.externalmovementsapi.sync.internal
22

33
import com.fasterxml.jackson.databind.ObjectMapper
4-
import com.fasterxml.jackson.module.kotlin.treeToValue
54
import com.microsoft.applicationinsights.TelemetryClient
65
import org.springframework.stereotype.Service
76
import org.springframework.transaction.annotation.Transactional
@@ -18,6 +17,7 @@ import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.Re
1817
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataRepository
1918
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataRequired
2019
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.ReferenceDataPaths
20+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.SingleSchedule
2121
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.TemporaryAbsenceAuthorisation
2222
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.TemporaryAbsenceAuthorisationRepository
2323
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.movement.TemporaryAbsenceMovement
@@ -63,7 +63,6 @@ import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.migrate.MigratedOc
6363
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.migrate.TapAuthorisation
6464
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.migrate.TapMovement
6565
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.migrate.TapOccurrence
66-
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.write.AuthorisationSchedule
6766
import java.time.LocalDate
6867
import java.time.LocalDateTime.of
6968
import java.util.UUID
@@ -264,13 +263,13 @@ class ResyncTapHierarchy(
264263
.forEach { auth ->
265264
val authOccurrences = occurrencesByAuthId[auth.id] ?: emptyList()
266265
if (authOccurrences.isEmpty()) {
267-
auth.occurrence(objectMapper)
266+
auth.occurrence()
268267
?.calculateStatus { code -> rdSupplier(OccurrenceStatus::class, code) as OccurrenceStatus }
269268
?.also(occurrenceRepository::save)
270269
} else {
271-
val schedule = auth.schedule?.let { objectMapper.treeToValue<AuthorisationSchedule>(it) }
272270
// if legacyId is not null this has likely been updated from nomis
273-
authOccurrences.single().takeIf { it.dpsOnly }?.updateFrom(auth, rdSupplier, schedule)
271+
authOccurrences.single().takeIf { it.dpsOnly }
272+
?.updateFrom(auth, rdSupplier, auth.schedule.takeIf { it is SingleSchedule } as? SingleSchedule)
274273
}
275274
}
276275
}
@@ -307,7 +306,7 @@ class ResyncTapHierarchy(
307306
comments = comments,
308307
start = start,
309308
end = end,
310-
schedule = schedule()?.let { objectMapper.valueToTree(it) },
309+
schedule = schedule(),
311310
reasonPath = reasonPath,
312311
locations = occurrences.mapTo(linkedSetOf()) { it.location }.takeIf { it.isNotEmpty() }
313312
?: location?.takeUnless(Location::isNullOrEmpty)?.let { linkedSetOf(it) } ?: linkedSetOf(),
@@ -327,7 +326,7 @@ class ResyncTapHierarchy(
327326
applyLogistics(request, rdPaths)
328327
applyComments(ChangeAuthorisationComments(request.comments))
329328
applyLegacyId(request.legacyId)
330-
request.schedule()?.also { applySchedule(objectMapper.valueToTree(it)) }
329+
request.schedule()?.also { applySchedule(it) }
331330
(
332331
request.occurrences.mapNotNullTo(linkedSetOf()) { it.location.takeUnless(Location::isNullOrEmpty) }
333332
.takeIf { it.isNotEmpty() }
@@ -403,7 +402,7 @@ class ResyncTapHierarchy(
403402
private fun TemporaryAbsenceOccurrence.updateFrom(
404403
authorisation: TemporaryAbsenceAuthorisation,
405404
rdSupplier: (KClass<out ReferenceData>, String) -> ReferenceData,
406-
schedule: AuthorisationSchedule?,
405+
schedule: SingleSchedule?,
407406
) = apply {
408407
authorisationPersonAndPrison(authorisation)
409408
applyAbsenceCategorisation(

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/sync/internal/SyncTapAuthorisation.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package uk.gov.justice.digital.hmpps.externalmovementsapi.sync.internal
22

3-
import com.fasterxml.jackson.databind.ObjectMapper
43
import org.springframework.data.repository.findByIdOrNull
54
import org.springframework.stereotype.Service
65
import org.springframework.transaction.annotation.Transactional
@@ -64,7 +63,6 @@ class SyncTapAuthorisation(
6463
private val referenceDataRepository: ReferenceDataRepository,
6564
private val authorisationRepository: TemporaryAbsenceAuthorisationRepository,
6665
private val occurrenceRepository: TemporaryAbsenceOccurrenceRepository,
67-
private val objectMapper: ObjectMapper,
6866
) {
6967
fun sync(personIdentifier: String, request: TapAuthorisation): SyncResponse {
7068
val person = personSummaryService.getWithSave(personIdentifier)
@@ -81,7 +79,7 @@ class SyncTapAuthorisation(
8179
ExternalMovementContext.get().copy(requestAt = request.created.at, username = request.created.by).set()
8280
val saved = authorisationRepository.save(request.asEntity(person, rdPaths))
8381
if (!saved.repeat && saved.schedule != null && saved.status.code !in listOf(APPROVED.name, EXPIRED.name)) {
84-
saved.createOccurrence(objectMapper, rdPaths)
82+
saved.createOccurrence(rdPaths)
8583
}
8684
saved
8785
}
@@ -145,7 +143,7 @@ class SyncTapAuthorisation(
145143
end = end,
146144
locations = location?.takeUnless(Location::isNullOrEmpty)?.let { linkedSetOf(it) } ?: linkedSetOf(),
147145
reasonPath = reasonPath,
148-
schedule = schedule()?.let { objectMapper.valueToTree(it) },
146+
schedule = schedule(),
149147
legacyId = legacyId,
150148
id = id ?: newUuid(),
151149
)
@@ -177,15 +175,14 @@ class SyncTapAuthorisation(
177175
} else {
178176
occ.updateFrom(this, request, rdPaths)
179177
}
180-
} ?: createOccurrence(objectMapper, rdPaths)
178+
} ?: createOccurrence(rdPaths)
181179
}
182180
}
183181

184182
private fun TemporaryAbsenceAuthorisation.createOccurrence(
185-
objectMapper: ObjectMapper,
186183
rdPaths: ReferenceDataPaths,
187184
) {
188-
occurrence(objectMapper)?.calculateStatus { code ->
185+
occurrence()?.calculateStatus { code ->
189186
rdPaths.getReferenceData(OccurrenceStatus::class, code) as OccurrenceStatus
190187
}?.also(occurrenceRepository::save)
191188
}
@@ -246,6 +243,6 @@ class SyncTapAuthorisation(
246243

247244
private fun TemporaryAbsenceAuthorisation.checkSchedule(request: TapAuthorisation, rdPaths: ReferenceDataPaths) {
248245
applyDateRange(ChangeAuthorisationDateRange(request.start, request.end), rdPaths::getReferenceData)
249-
request.schedule()?.also { applySchedule(objectMapper.valueToTree(it)) }
246+
request.schedule()?.also { applySchedule(it) }
250247
}
251248
}

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/sync/migrate/MigrateTapRequest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.Re
1111
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataDomain.Code.TRANSPORT
1212
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataRequired
1313
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.of
14+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.AuthorisationSchedule
15+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.SingleSchedule
1416
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.movement.TemporaryAbsenceMovement
1517
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.referencedata.AuthorisationStatus
1618
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.referencedata.OccurrenceStatus
@@ -19,7 +21,6 @@ import uk.gov.justice.digital.hmpps.externalmovementsapi.events.TemporaryAbsence
1921
import uk.gov.justice.digital.hmpps.externalmovementsapi.events.TemporaryAbsenceMigrated
2022
import uk.gov.justice.digital.hmpps.externalmovementsapi.model.location.Location
2123
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.AtAndBy
22-
import uk.gov.justice.digital.hmpps.externalmovementsapi.sync.write.AuthorisationSchedule
2324
import java.time.LocalDate
2425
import java.time.LocalDateTime
2526
import java.time.LocalTime
@@ -94,7 +95,7 @@ data class TapAuthorisation(
9495
)
9596

9697
fun schedule(): AuthorisationSchedule? = if (!repeat && occurrences.isEmpty()) {
97-
AuthorisationSchedule(startTime, endTime)
98+
SingleSchedule(startTime, endTime)
9899
} else {
99100
null
100101
}

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/sync/write/AuthorisationSchedule.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/main/kotlin/uk/gov/justice/digital/hmpps/externalmovementsapi/sync/write/TapAuthorisation.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.Re
1010
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataDomain.Code.TRANSPORT
1111
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.ReferenceDataRequired
1212
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.referencedata.of
13+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.AuthorisationSchedule
14+
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.authorisation.SingleSchedule
1315
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.referencedata.AuthorisationStatus
1416
import uk.gov.justice.digital.hmpps.externalmovementsapi.domain.tap.referencedata.OccurrenceStatus
1517
import uk.gov.justice.digital.hmpps.externalmovementsapi.model.location.Location
@@ -50,7 +52,7 @@ data class TapAuthorisation(
5052
) + OccurrenceStatus.Code.entries.map { TAP_OCCURRENCE_STATUS of it.name }
5153

5254
fun schedule(): AuthorisationSchedule? = if (!repeat) {
53-
AuthorisationSchedule(startTime, endTime)
55+
SingleSchedule(startTime, endTime)
5456
} else {
5557
null
5658
}

0 commit comments

Comments
 (0)