Skip to content

Commit a5404b9

Browse files
PRC-439: Sync CRUD and unit tests for the organisation entity. (#9)
1 parent a2e7446 commit a5404b9

16 files changed

+939
-20
lines changed

src/main/kotlin/uk/gov/justice/digital/hmpps/organisationsapi/facade/OrganisationFacade.kt

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class OrganisationFacade(
1919
fun create(request: CreateOrganisationRequest): OrganisationDetails = organisationService.create(request).also {
2020
outboundEventsService.send(
2121
outboundEvent = OutboundEvent.ORGANISATION_CREATED,
22+
organisationId = it.organisationId,
2223
identifier = it.organisationId,
2324
)
2425
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package uk.gov.justice.digital.hmpps.organisationsapi.facade
2+
3+
import org.springframework.stereotype.Service
4+
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
5+
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncUpdateOrganisationRequest
6+
import uk.gov.justice.digital.hmpps.organisationsapi.service.events.OutboundEvent
7+
import uk.gov.justice.digital.hmpps.organisationsapi.service.events.OutboundEventsService
8+
import uk.gov.justice.digital.hmpps.organisationsapi.service.events.Source
9+
import uk.gov.justice.digital.hmpps.organisationsapi.service.sync.SyncOrganisationService
10+
11+
/**
12+
* This class is a facade over the sync services as a thin layer
13+
* which is called by the sync controllers and in-turn calls the sync
14+
* service methods.
15+
*
16+
* Each method provides two purposes:
17+
* - To call the underlying sync services and apply the changes in a transactional method.
18+
* - To generate a domain event to inform subscribed services what has happened.
19+
*
20+
* All events generated as a result of a sync operation should generate domain events with the
21+
* additionalInformation.source = "NOMIS" to indicate that the actual source of the change
22+
* was NOMIS.
23+
*
24+
* This is important as the Syscon sync service will ignore domain events with
25+
* a source of NOMIS, but will action those with a source of DPS for changes which
26+
* originate within this service via the UI or API clients.
27+
*/
28+
29+
@Service
30+
class SyncFacade(
31+
private val syncService: SyncOrganisationService,
32+
private val outboundEventsService: OutboundEventsService,
33+
) {
34+
// ================================================================
35+
// Organisations
36+
// ================================================================
37+
38+
fun getOrganisationById(organisationId: Long) = syncService.getOrganisationById(organisationId)
39+
40+
fun createOrganisation(request: SyncCreateOrganisationRequest) = syncService.createOrganisation(request)
41+
.also {
42+
outboundEventsService.send(
43+
outboundEvent = OutboundEvent.ORGANISATION_CREATED,
44+
organisationId = it.organisationId,
45+
identifier = it.organisationId,
46+
source = Source.NOMIS,
47+
)
48+
}
49+
50+
fun updateOrganisation(organisationId: Long, request: SyncUpdateOrganisationRequest) = syncService.updateOrganisation(organisationId, request)
51+
.also {
52+
outboundEventsService.send(
53+
outboundEvent = OutboundEvent.ORGANISATION_UPDATED,
54+
organisationId = organisationId,
55+
identifier = organisationId,
56+
source = Source.NOMIS,
57+
)
58+
}
59+
60+
fun deleteOrganisation(organisationId: Long) = syncService.deleteOrganisation(organisationId)
61+
.also {
62+
outboundEventsService.send(
63+
outboundEvent = OutboundEvent.ORGANISATION_DELETED,
64+
organisationId = organisationId,
65+
identifier = organisationId,
66+
source = Source.NOMIS,
67+
)
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package uk.gov.justice.digital.hmpps.organisationsapi.mapping.sync
2+
3+
import uk.gov.justice.digital.hmpps.organisationsapi.entity.OrganisationWithFixedIdEntity
4+
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
5+
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationResponse
6+
7+
fun OrganisationWithFixedIdEntity.toModel(): SyncOrganisationResponse = SyncOrganisationResponse(
8+
organisationId = this.id(),
9+
organisationName = this.organisationName,
10+
programmeNumber = this.programmeNumber,
11+
vatNumber = this.vatNumber,
12+
caseloadId = this.caseloadId,
13+
comments = this.comments,
14+
active = this.active,
15+
deactivatedDate = this.deactivatedDate,
16+
createdBy = this.createdBy,
17+
createdTime = this.createdTime,
18+
updatedBy = this.updatedBy,
19+
updatedTime = this.updatedTime,
20+
)
21+
22+
fun List<OrganisationWithFixedIdEntity>.toModel(): List<SyncOrganisationResponse> = map { it.toModel() }
23+
24+
fun SyncCreateOrganisationRequest.toEntity() = OrganisationWithFixedIdEntity(
25+
organisationId = this.organisationId,
26+
organisationName = this.organisationName,
27+
programmeNumber = this.programmeNumber,
28+
vatNumber = this.vatNumber,
29+
caseloadId = this.caseloadId,
30+
comments = this.comments,
31+
active = this.active,
32+
deactivatedDate = this.deactivatedDate,
33+
createdBy = this.createdBy,
34+
createdTime = this.createdTime,
35+
updatedBy = this.updatedBy,
36+
updatedTime = this.updatedTime,
37+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotNull
5+
import jakarta.validation.constraints.Size
6+
import java.time.LocalDate
7+
import java.time.LocalDateTime
8+
9+
/**
10+
The difference between a SyncCreateOrganisation and a CreateOrganisationRequest is only
11+
that the sync version accepts an Organisation ID field and has less stringent validation, so
12+
it accepts more of the range of values that the sync service provides from NOMIS.
13+
14+
The standard CreateOrganisationRequest is used in CRUD endpoints intended for use by the DPS
15+
UI service (and other DPS clients) and does not specify an organisation ID because this will be
16+
generated from a DPS range, and also has a higher standard of validation to ensure it captures
17+
more accurate values.
18+
*/
19+
@Schema(description = "Request to create a new organisation")
20+
data class SyncCreateOrganisationRequest(
21+
22+
@Schema(description = "The organisation ID AKA the corporate ID from NOMIS", example = "1233323")
23+
@field:NotNull(message = "The organisation ID must be present in this request")
24+
val organisationId: Long,
25+
26+
@Schema(description = "The name of the organisation", example = "Example Limited", maxLength = 40)
27+
@field:Size(max = 40, message = "organisationName must be <= 40 characters")
28+
val organisationName: String,
29+
30+
@Schema(
31+
description = "The programme number for the organisation, stored as FEI_NUMBER in NOMIS",
32+
example = "1",
33+
maxLength = 40,
34+
nullable = true,
35+
)
36+
@field:Size(max = 40, message = "programmeNumber must be <= 40 characters")
37+
val programmeNumber: String? = null,
38+
39+
@Schema(description = "The VAT number for the organisation, if known", example = "123456", maxLength = 12, nullable = true)
40+
@field:Size(max = 12, message = "vatNumber must be <= 12 characters")
41+
val vatNumber: String? = null,
42+
43+
@Schema(
44+
description = "The id of the caseload for this organisation, this is an agency id in NOMIS",
45+
example = "BXI",
46+
maxLength = 6,
47+
nullable = true,
48+
)
49+
@field:Size(max = 6, message = "caseloadId must be <= 6 characters")
50+
val caseloadId: String? = null,
51+
52+
@Schema(description = "Any comments on the organisation", example = "Some additional info", maxLength = 240, nullable = true)
53+
@field:Size(max = 240, message = "comments must be <= 240 characters")
54+
val comments: String? = null,
55+
56+
@Schema(description = "Whether the organisation is active or not", example = "true")
57+
val active: Boolean = false,
58+
59+
@Schema(description = "The date the organisation was deactivated, EXPIRY_DATE in NOMIS", example = "2010-12-30", nullable = true)
60+
val deactivatedDate: LocalDate? = null,
61+
62+
@Schema(description = "User who created the entry", example = "admin")
63+
val createdBy: String,
64+
65+
@Schema(description = "Timestamp when the entry was created", example = "2023-09-23T10:15:30")
66+
val createdTime: LocalDateTime,
67+
68+
@Schema(description = "User who updated the entry", example = "admin2")
69+
val updatedBy: String? = null,
70+
71+
@Schema(description = "Timestamp when the entry was updated", example = "2023-09-24T12:00:00")
72+
val updatedTime: LocalDateTime? = null,
73+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotNull
5+
import jakarta.validation.constraints.Size
6+
import java.time.LocalDate
7+
import java.time.LocalDateTime
8+
9+
data class SyncUpdateOrganisationRequest(
10+
@Schema(description = "The organisation ID AKA the corporate ID from NOMIS", example = "1233323")
11+
@field:NotNull(message = "The organisation ID must be present in this request")
12+
val organisationId: Long,
13+
14+
@Schema(description = "The name of the organisation", example = "Example Limited", maxLength = 40)
15+
@field:Size(max = 40, message = "organisationName must be <= 40 characters")
16+
val organisationName: String,
17+
18+
@Schema(
19+
description = "The programme number for the organisation, stored as FEI_NUMBER in NOMIS",
20+
example = "1",
21+
maxLength = 40,
22+
nullable = true,
23+
)
24+
@field:Size(max = 40, message = "programmeNumber must be <= 40 characters")
25+
val programmeNumber: String? = null,
26+
27+
@Schema(description = "The VAT number for the organisation, if known", example = "123456", maxLength = 12, nullable = true)
28+
@field:Size(max = 12, message = "vatNumber must be <= 12 characters")
29+
val vatNumber: String? = null,
30+
31+
@Schema(
32+
description = "The id of the caseload for this organisation, this is an agency id in NOMIS",
33+
example = "BXI",
34+
maxLength = 6,
35+
nullable = true,
36+
)
37+
@field:Size(max = 6, message = "caseloadId must be <= 6 characters")
38+
val caseloadId: String? = null,
39+
40+
@Schema(description = "Any comments on the organisation", example = "Some additional info", maxLength = 240, nullable = true)
41+
@field:Size(max = 240, message = "comments must be <= 240 characters")
42+
val comments: String? = null,
43+
44+
@Schema(description = "Whether the organisation is active or not", example = "true")
45+
val active: Boolean = false,
46+
47+
@Schema(description = "The date the organisation was deactivated, EXPIRY_DATE in NOMIS", example = "2010-12-30", nullable = true)
48+
val deactivatedDate: LocalDate? = null,
49+
50+
@Schema(description = "User who updated the entry", example = "admin2")
51+
val updatedBy: String,
52+
53+
@Schema(description = "Timestamp when the entry was updated", example = "2023-09-24T12:00:00")
54+
val updatedTime: LocalDateTime,
55+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import java.time.LocalDate
5+
import java.time.LocalDateTime
6+
7+
data class SyncOrganisationResponse(
8+
@Schema(description = "The id of the organisation", example = "123456")
9+
val organisationId: Long,
10+
11+
@Schema(description = "Organisation name", example = "Supplier Services plc", nullable = true)
12+
val organisationName: String? = null,
13+
14+
@Schema(description = "Programme number", example = "8765", nullable = true)
15+
val programmeNumber: String? = null,
16+
17+
@Schema(description = "VAT number", example = "GB 55 55 55 55", nullable = true)
18+
val vatNumber: String? = null,
19+
20+
@Schema(description = "Caseload ID (a specific prison)", example = "HEI", nullable = true)
21+
val caseloadId: String? = null,
22+
23+
@Schema(description = "Comments related to this organisation", example = "Notes", nullable = true)
24+
val comments: String? = null,
25+
26+
@Schema(description = "Active flag", example = "true")
27+
var active: Boolean = false,
28+
29+
@Schema(description = "The date this organisation was deactivated", example = "2019-01-01", nullable = true)
30+
val deactivatedDate: LocalDate? = null,
31+
32+
@Schema(description = "User who created the organisation", example = "admin")
33+
val createdBy: String,
34+
35+
@Schema(description = "Timestamp when the organisation was created", example = "2023-09-23T10:15:30")
36+
val createdTime: LocalDateTime,
37+
38+
@Schema(description = "User who updated the organisation", example = "admin2")
39+
val updatedBy: String? = null,
40+
41+
@Schema(description = "Timestamp when the organisation was updated", example = "2023-09-24T12:00:00")
42+
val updatedTime: LocalDateTime? = null,
43+
)

src/main/kotlin/uk/gov/justice/digital/hmpps/organisationsapi/resource/migrate/MigrateOrganisationController.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ import org.springframework.web.bind.annotation.RequestMapping
1616
import org.springframework.web.bind.annotation.RestController
1717
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.migrate.MigrateOrganisationRequest
1818
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.migrate.MigrateOrganisationResponse
19-
import uk.gov.justice.digital.hmpps.organisationsapi.service.migrate.OrganisationMigrationService
19+
import uk.gov.justice.digital.hmpps.organisationsapi.service.migrate.MigrateOrganisationService
2020
import uk.gov.justice.digital.hmpps.organisationsapi.swagger.AuthApiResponses
2121
import uk.gov.justice.hmpps.kotlin.common.ErrorResponse
2222

2323
@Tag(name = "Migration and synchronisation")
2424
@RestController
2525
@RequestMapping(value = ["migrate/organisation"], produces = [MediaType.APPLICATION_JSON_VALUE])
2626
@AuthApiResponses
27-
class MigrateOrganisationController(val migrationService: OrganisationMigrationService) {
27+
class MigrateOrganisationController(val migrationService: MigrateOrganisationService) {
2828

2929
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
3030
@Operation(

0 commit comments

Comments
 (0)