Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PRC-439: Sync CRUD and unit tests for the organisation entity. #9

Merged
merged 1 commit into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class OrganisationFacade(
fun create(request: CreateOrganisationRequest): OrganisationDetails = organisationService.create(request).also {
outboundEventsService.send(
outboundEvent = OutboundEvent.ORGANISATION_CREATED,
organisationId = it.organisationId,
identifier = it.organisationId,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package uk.gov.justice.digital.hmpps.organisationsapi.facade

import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncUpdateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.service.events.OutboundEvent
import uk.gov.justice.digital.hmpps.organisationsapi.service.events.OutboundEventsService
import uk.gov.justice.digital.hmpps.organisationsapi.service.events.Source
import uk.gov.justice.digital.hmpps.organisationsapi.service.sync.SyncOrganisationService

/**
* This class is a facade over the sync services as a thin layer
* which is called by the sync controllers and in-turn calls the sync
* service methods.
*
* Each method provides two purposes:
* - To call the underlying sync services and apply the changes in a transactional method.
* - To generate a domain event to inform subscribed services what has happened.
*
* All events generated as a result of a sync operation should generate domain events with the
* additionalInformation.source = "NOMIS" to indicate that the actual source of the change
* was NOMIS.
*
* This is important as the Syscon sync service will ignore domain events with
* a source of NOMIS, but will action those with a source of DPS for changes which
* originate within this service via the UI or API clients.
*/

@Service
class SyncFacade(
private val syncService: SyncOrganisationService,
private val outboundEventsService: OutboundEventsService,
) {
// ================================================================
// Organisations
// ================================================================

fun getOrganisationById(organisationId: Long) = syncService.getOrganisationById(organisationId)

fun createOrganisation(request: SyncCreateOrganisationRequest) = syncService.createOrganisation(request)
.also {
outboundEventsService.send(
outboundEvent = OutboundEvent.ORGANISATION_CREATED,
organisationId = it.organisationId,
identifier = it.organisationId,
source = Source.NOMIS,
)
}

fun updateOrganisation(organisationId: Long, request: SyncUpdateOrganisationRequest) = syncService.updateOrganisation(organisationId, request)
.also {
outboundEventsService.send(
outboundEvent = OutboundEvent.ORGANISATION_UPDATED,
organisationId = organisationId,
identifier = organisationId,
source = Source.NOMIS,
)
}

fun deleteOrganisation(organisationId: Long) = syncService.deleteOrganisation(organisationId)
.also {
outboundEventsService.send(
outboundEvent = OutboundEvent.ORGANISATION_DELETED,
organisationId = organisationId,
identifier = organisationId,
source = Source.NOMIS,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package uk.gov.justice.digital.hmpps.organisationsapi.mapping.sync

import uk.gov.justice.digital.hmpps.organisationsapi.entity.OrganisationWithFixedIdEntity
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync.SyncCreateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync.SyncOrganisationResponse

fun OrganisationWithFixedIdEntity.toModel(): SyncOrganisationResponse = SyncOrganisationResponse(
organisationId = this.id(),
organisationName = this.organisationName,
programmeNumber = this.programmeNumber,
vatNumber = this.vatNumber,
caseloadId = this.caseloadId,
comments = this.comments,
active = this.active,
deactivatedDate = this.deactivatedDate,
createdBy = this.createdBy,
createdTime = this.createdTime,
updatedBy = this.updatedBy,
updatedTime = this.updatedTime,
)

fun List<OrganisationWithFixedIdEntity>.toModel(): List<SyncOrganisationResponse> = map { it.toModel() }

fun SyncCreateOrganisationRequest.toEntity() = OrganisationWithFixedIdEntity(
organisationId = this.organisationId,
organisationName = this.organisationName,
programmeNumber = this.programmeNumber,
vatNumber = this.vatNumber,
caseloadId = this.caseloadId,
comments = this.comments,
active = this.active,
deactivatedDate = this.deactivatedDate,
createdBy = this.createdBy,
createdTime = this.createdTime,
updatedBy = this.updatedBy,
updatedTime = this.updatedTime,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import java.time.LocalDate
import java.time.LocalDateTime

/**
The difference between a SyncCreateOrganisation and a CreateOrganisationRequest is only
that the sync version accepts an Organisation ID field and has less stringent validation, so
it accepts more of the range of values that the sync service provides from NOMIS.

The standard CreateOrganisationRequest is used in CRUD endpoints intended for use by the DPS
UI service (and other DPS clients) and does not specify an organisation ID because this will be
generated from a DPS range, and also has a higher standard of validation to ensure it captures
more accurate values.
*/
@Schema(description = "Request to create a new organisation")
data class SyncCreateOrganisationRequest(

@Schema(description = "The organisation ID AKA the corporate ID from NOMIS", example = "1233323")
@field:NotNull(message = "The organisation ID must be present in this request")
val organisationId: Long,

@Schema(description = "The name of the organisation", example = "Example Limited", maxLength = 40)
@field:Size(max = 40, message = "organisationName must be <= 40 characters")
val organisationName: String,

@Schema(
description = "The programme number for the organisation, stored as FEI_NUMBER in NOMIS",
example = "1",
maxLength = 40,
nullable = true,
)
@field:Size(max = 40, message = "programmeNumber must be <= 40 characters")
val programmeNumber: String? = null,

@Schema(description = "The VAT number for the organisation, if known", example = "123456", maxLength = 12, nullable = true)
@field:Size(max = 12, message = "vatNumber must be <= 12 characters")
val vatNumber: String? = null,

@Schema(
description = "The id of the caseload for this organisation, this is an agency id in NOMIS",
example = "BXI",
maxLength = 6,
nullable = true,
)
@field:Size(max = 6, message = "caseloadId must be <= 6 characters")
val caseloadId: String? = null,

@Schema(description = "Any comments on the organisation", example = "Some additional info", maxLength = 240, nullable = true)
@field:Size(max = 240, message = "comments must be <= 240 characters")
val comments: String? = null,

@Schema(description = "Whether the organisation is active or not", example = "true")
val active: Boolean = false,

@Schema(description = "The date the organisation was deactivated, EXPIRY_DATE in NOMIS", example = "2010-12-30", nullable = true)
val deactivatedDate: LocalDate? = null,

@Schema(description = "User who created the entry", example = "admin")
val createdBy: String,

@Schema(description = "Timestamp when the entry was created", example = "2023-09-23T10:15:30")
val createdTime: LocalDateTime,

@Schema(description = "User who updated the entry", example = "admin2")
val updatedBy: String? = null,

@Schema(description = "Timestamp when the entry was updated", example = "2023-09-24T12:00:00")
val updatedTime: LocalDateTime? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package uk.gov.justice.digital.hmpps.organisationsapi.model.request.sync

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size
import java.time.LocalDate
import java.time.LocalDateTime

data class SyncUpdateOrganisationRequest(
@Schema(description = "The organisation ID AKA the corporate ID from NOMIS", example = "1233323")
@field:NotNull(message = "The organisation ID must be present in this request")
val organisationId: Long,

@Schema(description = "The name of the organisation", example = "Example Limited", maxLength = 40)
@field:Size(max = 40, message = "organisationName must be <= 40 characters")
val organisationName: String,

@Schema(
description = "The programme number for the organisation, stored as FEI_NUMBER in NOMIS",
example = "1",
maxLength = 40,
nullable = true,
)
@field:Size(max = 40, message = "programmeNumber must be <= 40 characters")
val programmeNumber: String? = null,

@Schema(description = "The VAT number for the organisation, if known", example = "123456", maxLength = 12, nullable = true)
@field:Size(max = 12, message = "vatNumber must be <= 12 characters")
val vatNumber: String? = null,

@Schema(
description = "The id of the caseload for this organisation, this is an agency id in NOMIS",
example = "BXI",
maxLength = 6,
nullable = true,
)
@field:Size(max = 6, message = "caseloadId must be <= 6 characters")
val caseloadId: String? = null,

@Schema(description = "Any comments on the organisation", example = "Some additional info", maxLength = 240, nullable = true)
@field:Size(max = 240, message = "comments must be <= 240 characters")
val comments: String? = null,

@Schema(description = "Whether the organisation is active or not", example = "true")
val active: Boolean = false,

@Schema(description = "The date the organisation was deactivated, EXPIRY_DATE in NOMIS", example = "2010-12-30", nullable = true)
val deactivatedDate: LocalDate? = null,

@Schema(description = "User who updated the entry", example = "admin2")
val updatedBy: String,

@Schema(description = "Timestamp when the entry was updated", example = "2023-09-24T12:00:00")
val updatedTime: LocalDateTime,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package uk.gov.justice.digital.hmpps.organisationsapi.model.response.sync

import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDate
import java.time.LocalDateTime

data class SyncOrganisationResponse(
@Schema(description = "The id of the organisation", example = "123456")
val organisationId: Long,

@Schema(description = "Organisation name", example = "Supplier Services plc", nullable = true)
val organisationName: String? = null,

@Schema(description = "Programme number", example = "8765", nullable = true)
val programmeNumber: String? = null,

@Schema(description = "VAT number", example = "GB 55 55 55 55", nullable = true)
val vatNumber: String? = null,

@Schema(description = "Caseload ID (a specific prison)", example = "HEI", nullable = true)
val caseloadId: String? = null,

@Schema(description = "Comments related to this organisation", example = "Notes", nullable = true)
val comments: String? = null,

@Schema(description = "Active flag", example = "true")
var active: Boolean = false,

@Schema(description = "The date this organisation was deactivated", example = "2019-01-01", nullable = true)
val deactivatedDate: LocalDate? = null,

@Schema(description = "User who created the organisation", example = "admin")
val createdBy: String,

@Schema(description = "Timestamp when the organisation was created", example = "2023-09-23T10:15:30")
val createdTime: LocalDateTime,

@Schema(description = "User who updated the organisation", example = "admin2")
val updatedBy: String? = null,

@Schema(description = "Timestamp when the organisation was updated", example = "2023-09-24T12:00:00")
val updatedTime: LocalDateTime? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import uk.gov.justice.digital.hmpps.organisationsapi.model.request.migrate.MigrateOrganisationRequest
import uk.gov.justice.digital.hmpps.organisationsapi.model.response.migrate.MigrateOrganisationResponse
import uk.gov.justice.digital.hmpps.organisationsapi.service.migrate.OrganisationMigrationService
import uk.gov.justice.digital.hmpps.organisationsapi.service.migrate.MigrateOrganisationService
import uk.gov.justice.digital.hmpps.organisationsapi.swagger.AuthApiResponses
import uk.gov.justice.hmpps.kotlin.common.ErrorResponse

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

@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
@Operation(
Expand Down
Loading