Skip to content

Commit 4de2a72

Browse files
committed
CDPS-1112: Get military records
1 parent c5c8858 commit 4de2a72

File tree

8 files changed

+474
-10
lines changed

8 files changed

+474
-10
lines changed

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/common/client/PrisonApiClient.kt

+8
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package uk.gov.justice.digital.hmpps.personintegrationapi.common.client
33
import org.springframework.http.ResponseEntity
44
import org.springframework.web.bind.annotation.PathVariable
55
import org.springframework.web.bind.annotation.RequestBody
6+
import org.springframework.web.service.annotation.GetExchange
67
import org.springframework.web.service.annotation.HttpExchange
78
import org.springframework.web.service.annotation.PutExchange
89
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.UpdateBirthCountry
910
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateBirthPlace
1011
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateNationality
12+
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.response.MilitaryRecord
13+
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.response.MilitaryRecordPrisonDto
1114

1215
@HttpExchange("/api/offenders")
1316
interface PrisonApiClient {
@@ -28,4 +31,9 @@ interface PrisonApiClient {
2831
@PathVariable offenderNo: String,
2932
@RequestBody updateNationality: UpdateNationality,
3033
): ResponseEntity<Void>
34+
35+
@GetExchange("/{offenderNo}/military-records")
36+
fun getMilitaryRecords(
37+
@PathVariable offenderNo: String,
38+
): ResponseEntity<MilitaryRecordPrisonDto>
3139
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package uk.gov.justice.digital.hmpps.personintegrationapi.common.client.response
2+
3+
import java.time.LocalDate
4+
5+
data class MilitaryRecordPrisonDto(
6+
val militaryRecords: List<MilitaryRecord>
7+
)
8+
9+
data class MilitaryRecord(
10+
val warZoneCode: String?,
11+
val warZoneDescription: String?,
12+
val startDate: LocalDate,
13+
val endDate: LocalDate?,
14+
val militaryDischargeCode: String?,
15+
val militaryDischargeDescription: String?,
16+
val militaryBranchCode: String,
17+
val militaryBranchDescription: String,
18+
val description: String?,
19+
val unitNumber: String?,
20+
val enlistmentLocation: String?,
21+
val dischargeLocation: String?,
22+
val selectiveServicesFlag: Boolean,
23+
val militaryRankCode: String?,
24+
val militaryRankDescription: String?,
25+
val serviceNumber: String?,
26+
val disciplinaryActionCode: String?,
27+
val disciplinaryActionDescription: String?
28+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.response
2+
3+
import java.time.LocalDate
4+
import com.fasterxml.jackson.annotation.JsonInclude
5+
import com.fasterxml.jackson.annotation.JsonInclude.Include
6+
import io.swagger.v3.oas.annotations.media.Schema
7+
8+
@JsonInclude(Include.NON_NULL)
9+
@Schema(description = "DTO representing a military record with details about service in the UK Armed Forces.")
10+
data class MilitaryRecordDto(
11+
@Schema(
12+
description = "Code identifying the war zone where the service took place.",
13+
example = "AFG"
14+
)
15+
val warZoneCode: String?,
16+
17+
@Schema(
18+
description = "Description of the war zone where the service took place.",
19+
example = "Afghanistan"
20+
)
21+
val warZoneDescription: String?,
22+
23+
@Schema(
24+
description = "Start date of the military service.",
25+
example = "2017-06-01"
26+
)
27+
val startDate: LocalDate,
28+
29+
@Schema(
30+
description = "End date of the military service, if applicable.",
31+
example = "2019-12-01"
32+
)
33+
val endDate: LocalDate?,
34+
35+
@Schema(
36+
description = "Code indicating the discharge status from the UK military forces.",
37+
example = "HON"
38+
)
39+
val militaryDischargeCode: String?,
40+
41+
@Schema(
42+
description = "Description of the discharge status from the UK military forces.",
43+
example = "Honourable"
44+
)
45+
val militaryDischargeDescription: String?,
46+
47+
@Schema(
48+
description = "Code identifying the branch of the UK military in which the individual served.",
49+
example = "ARM"
50+
)
51+
val militaryBranchCode: String,
52+
53+
@Schema(
54+
description = "Description of the military branch of the UK Armed Forces.",
55+
example = "Army"
56+
)
57+
val militaryBranchDescription: String,
58+
59+
@Schema(
60+
description = "Additional notes or details about the military service.",
61+
example = "Deployed to Afghanistan in support of Operation Herrick."
62+
)
63+
val description: String?,
64+
65+
@Schema(
66+
description = "Unit number in which the individual served.",
67+
example = "2nd Battalion, The Royal Anglian Regiment"
68+
)
69+
val unitNumber: String?,
70+
71+
@Schema(
72+
description = "Location where the individual enlisted in the UK military.",
73+
example = "Windsor, Berkshire"
74+
)
75+
val enlistmentLocation: String?,
76+
77+
@Schema(
78+
description = "Location where the individual was discharged from the UK military.",
79+
example = "Colchester, Essex"
80+
)
81+
val dischargeLocation: String?,
82+
83+
@Schema(
84+
description = "Flag indicating if the individual was registered for UK selective military service (National Service).",
85+
example = "false"
86+
)
87+
val selectiveServicesFlag: Boolean,
88+
89+
@Schema(
90+
description = "Code identifying the individual's military rank in the UK forces.",
91+
example = "CPL_ARM"
92+
)
93+
val militaryRankCode: String?,
94+
95+
@Schema(
96+
description = "Description of the individual's military rank in the UK forces.",
97+
example = "Corporal"
98+
)
99+
val militaryRankDescription: String?,
100+
101+
@Schema(
102+
description = "Service number of the individual within the UK military.",
103+
example = "2345678"
104+
)
105+
val serviceNumber: String?,
106+
107+
@Schema(
108+
description = "Code identifying any disciplinary actions taken against the individual during service.",
109+
example = "CM"
110+
)
111+
val disciplinaryActionCode: String?,
112+
113+
@Schema(
114+
description = "Description of any disciplinary actions taken against the individual during service.",
115+
example = "Court Martial"
116+
)
117+
val disciplinaryActionDescription: String?
118+
)

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1Resource.kt

+40
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.springframework.web.multipart.MultipartFile
2626
import uk.gov.justice.digital.hmpps.personintegrationapi.common.annotation.ValidPrisonerNumber
2727
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
2828
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.CorePersonRecordRoleConstants
29+
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.response.MilitaryRecordDto
2930
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CorePersonRecordV1UpdateRequestDto
3031
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.service.CorePersonRecordService
3132
import uk.gov.justice.hmpps.kotlin.common.ErrorResponse
@@ -189,4 +190,43 @@ class CorePersonRecordV1Resource(
189190
example = "COUNTRY",
190191
) domain: String,
191192
): ResponseEntity<List<ReferenceDataCodeDto>> = corePersonRecordService.getReferenceDataCodes(domain)
193+
194+
@GetMapping("military-records")
195+
@ResponseStatus(HttpStatus.OK)
196+
@Operation(
197+
summary = "Get military records for the given prisoner number",
198+
description = "Returns the list of military records for the prisoner. " +
199+
"Requires role `${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_ROLE}` or `${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE}`",
200+
responses = [
201+
ApiResponse(
202+
responseCode = "200",
203+
description = "Military records found",
204+
content = [Content(array = ArraySchema(schema = Schema(implementation = ReferenceDataCodeDto::class)))],
205+
),
206+
ApiResponse(
207+
responseCode = "401",
208+
description = "Unauthorised, requires a valid Oauth2 token",
209+
content = [Content(schema = Schema(implementation = ErrorResponse::class))],
210+
),
211+
ApiResponse(
212+
responseCode = "403",
213+
description = "Missing required role. Requires ${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_ROLE} or ${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE}.",
214+
content = [
215+
Content(
216+
mediaType = MediaType.APPLICATION_JSON_VALUE,
217+
schema = Schema(implementation = ErrorResponse::class),
218+
),
219+
],
220+
),
221+
ApiResponse(
222+
responseCode = "404",
223+
description = "Prisoner not found",
224+
content = [Content(schema = Schema(implementation = ErrorResponse::class))],
225+
),
226+
],
227+
)
228+
@PreAuthorize("hasAnyRole('${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_ROLE}', '${CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_WRITE_ROLE}')")
229+
fun getMilitaryRecords(
230+
@RequestParam(required = true) @Valid @ValidPrisonerNumber prisonerNumber: String,
231+
): ResponseEntity<List<MilitaryRecordDto>> = corePersonRecordService.getMilitaryRecords(prisonerNumber)
192232
}

src/main/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/service/CorePersonRecordService.kt

+39
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.dto.Updat
88
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateBirthPlace
99
import uk.gov.justice.digital.hmpps.personintegrationapi.common.client.request.UpdateNationality
1010
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
11+
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.response.MilitaryRecordDto
1112
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.BirthplaceUpdateDto
1213
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CorePersonRecordV1UpdateRequestDto
1314
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.v1.request.CountryOfBirthUpdateDto
@@ -59,4 +60,42 @@ class CorePersonRecordService(
5960
return ResponseEntity.status(response.statusCode).build()
6061
}
6162
}
63+
64+
fun getMilitaryRecords(prisonerNumber: String): ResponseEntity<List<MilitaryRecordDto>> {
65+
val response = prisonApiClient.getMilitaryRecords(prisonerNumber)
66+
67+
if (response.statusCode.is2xxSuccessful) {
68+
val rankSuffixList = setOf("\\(Army\\)", "\\(Navy\\)", "\\(RAF\\)", "\\(Royal Marines\\)")
69+
val rankSuffixPattern = Regex(rankSuffixList.joinToString("|"), RegexOption.IGNORE_CASE)
70+
71+
val mappedResponse = response.body?.militaryRecords?.map {
72+
it.copy(
73+
militaryRankDescription = it.militaryRankDescription?.replace(rankSuffixPattern, "")?.trim(),
74+
)
75+
MilitaryRecordDto(
76+
warZoneCode = it.warZoneCode,
77+
warZoneDescription = it.warZoneDescription,
78+
startDate = it.startDate,
79+
endDate = it.endDate,
80+
militaryDischargeCode = it.militaryDischargeCode,
81+
militaryDischargeDescription = it.militaryDischargeDescription,
82+
militaryBranchCode = it.militaryBranchCode,
83+
militaryBranchDescription = it.militaryBranchDescription,
84+
description = it.description,
85+
unitNumber = it.unitNumber,
86+
enlistmentLocation = it.enlistmentLocation,
87+
dischargeLocation = it.dischargeLocation,
88+
selectiveServicesFlag = it.selectiveServicesFlag,
89+
militaryRankCode = it.militaryRankCode,
90+
militaryRankDescription = it.militaryRankDescription?.replace(rankSuffixPattern, "")?.trim(),
91+
serviceNumber = it.serviceNumber,
92+
disciplinaryActionCode = it.disciplinaryActionCode,
93+
disciplinaryActionDescription = it.disciplinaryActionDescription,
94+
)
95+
}
96+
return ResponseEntity.ok(mappedResponse)
97+
} else {
98+
return ResponseEntity.status(response.statusCode).build()
99+
}
100+
}
62101
}

src/test/kotlin/uk/gov/justice/digital/hmpps/personintegrationapi/corepersonrecord/resource/CorePersonRecordV1ResourceIntTest.kt

+85
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import org.springframework.web.multipart.MultipartFile
1212
import org.springframework.web.reactive.function.BodyInserters
1313
import uk.gov.justice.digital.hmpps.personintegrationapi.common.dto.ReferenceDataCodeDto
1414
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.CorePersonRecordRoleConstants
15+
import uk.gov.justice.digital.hmpps.personintegrationapi.corepersonrecord.dto.response.MilitaryRecordDto
1516
import uk.gov.justice.digital.hmpps.personintegrationapi.integration.IntegrationTestBase
1617
import uk.gov.justice.digital.hmpps.personintegrationapi.integration.wiremock.PRISONER_NUMBER
1718
import uk.gov.justice.digital.hmpps.personintegrationapi.integration.wiremock.PRISONER_NUMBER_NOT_FOUND
1819
import uk.gov.justice.digital.hmpps.personintegrationapi.integration.wiremock.PRISON_API_NOT_FOUND_RESPONSE
20+
import java.time.LocalDate
1921

2022
class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
2123

@@ -196,6 +198,89 @@ class CorePersonRecordV1ResourceIntTest : IntegrationTestBase() {
196198
}
197199
}
198200

201+
@DisplayName("GET v1/core-person-record/military-records")
202+
@Nested
203+
inner class GetMilitaryRecords {
204+
205+
@Nested
206+
inner class Security {
207+
@Test
208+
fun `access forbidden when no authority`() {
209+
webTestClient.get().uri("/v1/core-person-record/military-records?prisonerNumber=$PRISONER_NUMBER")
210+
.exchange()
211+
.expectStatus().isUnauthorized
212+
}
213+
214+
@Test
215+
fun `access forbidden with wrong role`() {
216+
webTestClient.get().uri("/v1/core-person-record/military-records?prisonerNumber=$PRISONER_NUMBER")
217+
.headers(setAuthorisation(roles = listOf("ROLE_IS_WRONG")))
218+
.exchange()
219+
.expectStatus().isForbidden
220+
}
221+
}
222+
223+
@Nested
224+
inner class HappyPath {
225+
226+
@Test
227+
fun `can get military records for prisonerNumber`() {
228+
val response =
229+
webTestClient.get().uri("/v1/core-person-record/military-records?prisonerNumber=$PRISONER_NUMBER")
230+
.headers(setAuthorisation(roles = listOf(CorePersonRecordRoleConstants.CORE_PERSON_RECORD_READ_ROLE)))
231+
.exchange()
232+
.expectStatus().isOk
233+
.expectBodyList(MilitaryRecordDto::class.java)
234+
.returnResult().responseBody
235+
236+
assertThat(response).isEqualTo(
237+
listOf(
238+
MilitaryRecordDto(
239+
warZoneCode = "WZ1",
240+
warZoneDescription = "War Zone One",
241+
startDate = LocalDate.parse("2021-01-01"),
242+
endDate = LocalDate.parse("2021-12-31"),
243+
militaryDischargeCode = "MD1",
244+
militaryDischargeDescription = "Military Discharge One",
245+
militaryBranchCode = "MB1",
246+
militaryBranchDescription = "Military Branch One",
247+
description = "Description One",
248+
unitNumber = "Unit Number One",
249+
enlistmentLocation = "Enlistment Location One",
250+
dischargeLocation = "Discharge Location One",
251+
selectiveServicesFlag = true,
252+
militaryRankCode = "MR1",
253+
militaryRankDescription = "Military Rank One",
254+
serviceNumber = "Service Number One",
255+
disciplinaryActionCode = "DA1",
256+
disciplinaryActionDescription = "Disciplinary Action One"
257+
),
258+
MilitaryRecordDto(
259+
warZoneCode = "WZ2",
260+
warZoneDescription = "War Zone Two",
261+
startDate = LocalDate.parse("2022-01-01"),
262+
endDate = LocalDate.parse("2022-12-31"),
263+
militaryDischargeCode = "MD2",
264+
militaryDischargeDescription = "Military Discharge Two",
265+
militaryBranchCode = "MB2",
266+
militaryBranchDescription = "Military Branch Two",
267+
description = "Description Two",
268+
unitNumber = "Unit Number Two",
269+
enlistmentLocation = "Enlistment Location Two",
270+
dischargeLocation = "Discharge Location Two",
271+
selectiveServicesFlag = false,
272+
militaryRankCode = "MR2",
273+
militaryRankDescription = "Military Rank Two",
274+
serviceNumber = "Service Number Two",
275+
disciplinaryActionCode = "DA2",
276+
disciplinaryActionDescription = "Disciplinary Action Two",
277+
),
278+
),
279+
)
280+
}
281+
}
282+
}
283+
199284
private companion object {
200285

201286
val BIRTHPLACE_PATCH_REQUEST_BODY =

0 commit comments

Comments
 (0)