Skip to content

Commit

Permalink
PI-2107: Added documents grouped endpoint (#4152)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmcphee77 authored Aug 8, 2024
1 parent 87f4c37 commit 611a113
Show file tree
Hide file tree
Showing 20 changed files with 527 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package uk.gov.justice.digital.hmpps.data.entity
import jakarta.persistence.*
import org.hibernate.type.YesNoConverter
import java.time.LocalDate
import java.time.LocalDateTime

@Entity
class ApprovedPremisesReferral(
Expand Down Expand Up @@ -35,7 +36,9 @@ class InstitutionalReport(
@Convert(converter = YesNoConverter::class)
val establishment: Boolean,
val custodyId: Long,
val dateRequested: LocalDate
val dateRequested: LocalDate,
val dateRequired: LocalDate,
val dateCompleted: LocalDateTime,
)

@Entity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ object DocumentEntityGenerator {
institutionReportTypeId = INSTITUTIONAL_REPORT_TYPE.id,
custodyId = 1,
establishment = true,
dateRequested = LocalDate.of(2000, 1, 2)
dateRequested = LocalDate.of(2000, 1, 2),
dateRequired = LocalDate.of(2000, 1, 2),
dateCompleted = LocalDateTime.of(2000, 1, 2, 0, 0)
)

fun generateDocument(personId: Long, primaryKeyId: Long?, type: String, tableName: String?) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ package uk.gov.justice.digital.hmpps.data.generator
import uk.gov.justice.digital.hmpps.data.generator.SentenceGenerator.CURRENT_SENTENCE
import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.UpwAppointment
import uk.gov.justice.digital.hmpps.integrations.delius.event.sentence.entity.UpwDetails
import java.time.LocalDate

object UnpaidWorkGenerator {

val UNPAID_WORK_DETAILS_1 =
UpwDetails(IdGenerator.getAndIncrement(), CURRENT_SENTENCE, 0, ReferenceDataGenerator.HOURS_WORKED)

val APPT1 = UpwAppointment(IdGenerator.getAndIncrement(), 3, "Y", "Y", 0, UNPAID_WORK_DETAILS_1)
val APPT2 = UpwAppointment(IdGenerator.getAndIncrement(), 4, "Y", "Y", 1, UNPAID_WORK_DETAILS_1)
val APPT3 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "N", 1, UNPAID_WORK_DETAILS_1)
val APPT4 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "Y", 1, UNPAID_WORK_DETAILS_1)
val APPT5 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "Y", 1, UNPAID_WORK_DETAILS_1)
val APPT6 = UpwAppointment(IdGenerator.getAndIncrement(), 0, null, null, 1, UNPAID_WORK_DETAILS_1)
val APPT7 = UpwAppointment(IdGenerator.getAndIncrement(), 0, "Y", "Y", 0, UNPAID_WORK_DETAILS_1)
val APPT1 =
UpwAppointment(IdGenerator.getAndIncrement(), 3, "Y", "Y", 0, LocalDate.now(), 1L, UNPAID_WORK_DETAILS_1)
val APPT2 =
UpwAppointment(IdGenerator.getAndIncrement(), 4, "Y", "Y", 1, LocalDate.now(), 1L, UNPAID_WORK_DETAILS_1)
val APPT3 =
UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "N", 1, LocalDate.now(), 1L, UNPAID_WORK_DETAILS_1)
val APPT4 =
UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "Y", 1, LocalDate.now(), 1L, UNPAID_WORK_DETAILS_1)
val APPT5 =
UpwAppointment(IdGenerator.getAndIncrement(), 0, "N", "Y", 1, LocalDate.now(), 1L, UNPAID_WORK_DETAILS_1)
val APPT6 =
UpwAppointment(IdGenerator.getAndIncrement(), 0, null, null, 1, LocalDate.now(), 1L, UNPAID_WORK_DETAILS_1)
val APPT7 =
UpwAppointment(IdGenerator.getAndIncrement(), 0, "Y", "Y", 0, LocalDate.now(), 1L, UNPAID_WORK_DETAILS_1)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"documents": [],
"convictions": [
{
"convictionId": "83",
"documents": [
{
"id": "alfrescoId",
"documentName": "filename.txt",
"type": {
"code": "CONVICTION_DOCUMENT",
"description": "Sentence related"
},
"lastModifiedAt": "2024-08-08T15:56:02.88685",
"createdAt": "2024-08-08T15:56:02.88684",
"parentPrimaryKeyId": 83,
"reportDocumentDates": {}
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"mappings": [
{
"request": {
"method": "GET",
"urlPathTemplate": "/secure/offenders/crn/{crn}/documents/grouped"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "get_documents_grouped_C123456.json"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package uk.gov.justice.digital.hmpps

import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
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.status
import uk.gov.justice.digital.hmpps.api.model.OffenderDocuments
import uk.gov.justice.digital.hmpps.api.resource.advice.ErrorResponse
import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = RANDOM_PORT)
internal class DocumentsIntegrationTest {
@Autowired
lateinit var mockMvc: MockMvc

@Test
fun `call documents grouped by CRN`() {
val crn = PersonGenerator.CURRENTLY_MANAGED.crn

val response = mockMvc
.perform(get("/probation-case/$crn/documents/grouped").withToken())
.andExpect(status().isOk)
.andReturn().response.contentAsJson<OffenderDocuments>()

assertThat(response.documents.size, equalTo(0))
assertThat(response.convictions.size, equalTo(1))
}

@Test
fun `call documents grouped by CRN invalid filter`() {
val crn = PersonGenerator.CURRENTLY_MANAGED.crn

val response = mockMvc
.perform(get("/probation-case/$crn/documents/grouped?type=INVALID").withToken())
.andExpect(status().isBadRequest)
.andReturn().response.contentAsJson<ErrorResponse>()

assertThat(response.developerMessage, equalTo("type of INVALID was not valid"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ internal class ProxyIntegrationTest {
"CONVICTION_BY_ID_SENTENCE_STATUS": {
"convictionId": "?",
"activeOnly": true
},
"DOCUMENTS_GROUPED": {
"type": null,
"subtype": null
}
}
}
Expand All @@ -269,7 +273,7 @@ internal class ProxyIntegrationTest {
.withToken()
).andExpect(status().is2xxSuccessful).andReturn().response.contentAsJson<CompareAllReport>()

assertThat(res.totalNumberOfRequests, equalTo(14))
assertThat(res.totalNumberOfRequests, equalTo(15))
assertThat(res.totalNumberOfCrns, equalTo(2))
assertThat(res.currentPageNumber, equalTo(1))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package uk.gov.justice.digital.hmpps.api.model

import java.time.LocalDate
import java.time.LocalDateTime

data class OffenderDocumentDetail(
val id: String? = null,
val documentName: String? = null,
val author: String? = null,
val type: KeyValue,
val extendedDescription: String? = null,
val lastModifiedAt: LocalDateTime? = null,
val createdAt: LocalDateTime? = null,
val parentPrimaryKeyId: Long? = null,
val subType: KeyValue? = null,
val reportDocumentDates: ReportDocumentDates? = null
)

data class ReportDocumentDates(
val requestedDate: LocalDate? = null,
val requiredDate: LocalDate? = null,
val completedDate: LocalDateTime? = null
)

data class ConvictionDocuments(
val convictionId: String,
val documents: List<OffenderDocumentDetail> = emptyList()
)

data class OffenderDocuments(
val documents: List<OffenderDocumentDetail> = emptyList(),
val convictions: List<ConvictionDocuments> = emptyList(),
)

data class DocumentFilter(
val type: String? = null,
val subtype: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,12 @@ data class KeyValue(
val description: String
)

data class OffenderDocumentDetail(

val documentName: String,
val author: String?,
val type: DocumentType,
val extendedDescription: String?,
val createdAt: ZonedDateTime?,
val subType: KeyValue?
)

enum class DocumentType(val description: String) {
enum class DocumentType(val description: String, val subtypes: List<SubType> = emptyList()) {
OFFENDER_DOCUMENT("Offender related"),
CONVICTION_DOCUMENT("Sentence related"),
CPSPACK_DOCUMENT("Crown Prosecution Service case pack"),
PRECONS_DOCUMENT("PNC previous convictions"),
COURT_REPORT_DOCUMENT("Court report"),
COURT_REPORT_DOCUMENT("Court report", listOf(SubType.PSR)),
INSTITUTION_REPORT_DOCUMENT("Institution report"),
ADDRESS_ASSESSMENT_DOCUMENT("Address assessment related document"),
APPROVED_PREMISES_REFERRAL_DOCUMENT("Approved premises referral related document"),
Expand All @@ -100,6 +90,10 @@ enum class DocumentType(val description: String) {
PREVIOUS_CONVICTION("Previous conviction document")
}

enum class SubType {
PSR
}

class Breach(
val description: String?,
val status: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import uk.gov.justice.digital.hmpps.api.resource.ConvictionResource
import uk.gov.justice.digital.hmpps.api.resource.DocumentResource
import uk.gov.justice.digital.hmpps.api.resource.ProbationRecordResource
import uk.gov.justice.digital.hmpps.flags.FeatureFlags
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
Expand All @@ -24,6 +25,7 @@ class CommunityApiController(
private val featureFlags: FeatureFlags,
private val communityApiService: CommunityApiService,
private val convictionResource: ConvictionResource,
private val documentResource: DocumentResource,
private val taskExecutor: ThreadPoolTaskExecutor,
private val personRepository: PersonEventRepository,
private val telemetryService: TelemetryService,
Expand Down Expand Up @@ -292,6 +294,27 @@ class CommunityApiController(
return proxy(request)
}

@GetMapping("/offenders/crn/{crn}/documents/grouped")
fun documentsGrouped(
request: HttpServletRequest,
@PathVariable crn: String,
@RequestParam(required = false) type: String?,
@RequestParam(required = false) subtype: String?
): Any {

val params = mutableMapOf<String, String>()
type?.let { params["type"] = it }
subtype?.let { params["subtype"] = it }
sendComparisonReport(
params, Uri.DOCUMENTS_GROUPED, request
)

if (featureFlags.enabled("ccd-document-grouped")) {
return documentResource.getOffenderDocumentsGrouped(crn, type, subtype)
}
return proxy(request)
}

@GetMapping("/**")
fun proxy(request: HttpServletRequest): ResponseEntity<String> {
val headers = request.headerNames.asSequence().associateWith { request.getHeader(it) }.toMutableMap()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.client.HttpStatusCodeException
import uk.gov.justice.digital.hmpps.api.resource.advice.CommunityApiControllerAdvice
import uk.gov.justice.digital.hmpps.exception.InvalidRequestException
import uk.gov.justice.digital.hmpps.exception.NotFoundException
import java.io.StringReader
import java.lang.reflect.InvocationTargetException
Expand Down Expand Up @@ -45,6 +46,7 @@ class CommunityApiService(
when (val cause = ex.cause) {
is AccessDeniedException -> controllerAdvice.handleAccessDenied(cause).body
is NotFoundException -> controllerAdvice.handleNotFound(cause).body
is InvalidRequestException -> controllerAdvice.handleInvalidRequest(cause).body
else -> throw ex
}
}
Expand All @@ -68,10 +70,14 @@ class CommunityApiService(

val uri = Uri.valueOf(compare.uri)
val comApiUri = compare.params.entries.fold(uri.comApiUrl) { path, (key, value) ->
path.replace(
"{$key}",
value.toString()
)
if (value != null) {
path.replace(
"{$key}",
value.toString()
)
} else {
path.replace("$key={$key}", "")
}
}.replace(" ", "%20")

val ccdJsonString = try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package uk.gov.justice.digital.hmpps.api.proxy

data class Compare(
val params: Map<String, Any>,
val params: Map<String, Any?>,
val uri: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,11 @@ enum class Uri(
"getConvictionSentenceStatus",
listOf("crn", "convictionId"),
),
DOCUMENTS_GROUPED(
"/secure/offenders/crn/{crn}/documents/grouped?type={type}&subtype={subtype}",
"documentResource",
"getOffenderDocumentsGrouped",
listOf("crn", "type", "subtype"),
),
DUMMY("/dummy", "dummyResource", "getDummy", listOf("crn")),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package uk.gov.justice.digital.hmpps.api.resource

import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*
import uk.gov.justice.digital.hmpps.api.model.DocumentFilter
import uk.gov.justice.digital.hmpps.integrations.delius.service.DocumentService

@RestController
@RequestMapping("probation-case/{crn}/documents")
@PreAuthorize("hasRole('PROBATION_API__COURT_CASE__CASE_DETAIL')")
class DocumentResource(private val documentService: DocumentService) {

@GetMapping("/grouped")
fun getOffenderDocumentsGrouped(
@PathVariable crn: String,
@RequestParam(required = false) type: String?,
@RequestParam(required = false) subType: String?,
) = documentService.getDocumentsGroupedFor(crn, DocumentFilter(type, subType))
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package uk.gov.justice.digital.hmpps.api.resource.advice

import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatus.FORBIDDEN
import org.springframework.http.HttpStatus.NOT_FOUND
import org.springframework.http.ResponseEntity
import org.springframework.security.access.AccessDeniedException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import uk.gov.justice.digital.hmpps.api.resource.ConvictionResource
import uk.gov.justice.digital.hmpps.api.resource.DocumentResource
import uk.gov.justice.digital.hmpps.api.resource.ProbationRecordResource
import uk.gov.justice.digital.hmpps.exception.InvalidRequestException
import uk.gov.justice.digital.hmpps.exception.NotFoundException

@RestControllerAdvice(basePackageClasses = [ProbationRecordResource::class, ConvictionResource::class])
@RestControllerAdvice(basePackageClasses = [ProbationRecordResource::class, ConvictionResource::class, DocumentResource::class])
class CommunityApiControllerAdvice {

@ExceptionHandler(InvalidRequestException::class)
fun handleInvalidRequest(e: InvalidRequestException) = ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse(status = HttpStatus.BAD_REQUEST.value(), developerMessage = e.message))

@ExceptionHandler(NotFoundException::class)
fun handleNotFound(e: NotFoundException) = ResponseEntity
.status(NOT_FOUND)
Expand Down
Loading

0 comments on commit 611a113

Please sign in to comment.