Skip to content

Commit

Permalink
Simplification de la logique de récupération des PNOs logbook (#3547)
Browse files Browse the repository at this point in the history
## Linked issues

- Resolve #3543
  • Loading branch information
louptheron committed Aug 16, 2024
2 parents d00c97f + 7e52afd commit 6676dfa
Show file tree
Hide file tree
Showing 33 changed files with 622 additions and 1,118 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ run-back: run-stubbed-apis
docker compose up -d --quiet-pull --wait db keycloak
cd backend && ./gradlew bootRun --args='--spring.profiles.active=local --spring.config.additional-location=$(INFRA_FOLDER)'

run-back-for-cypress: run-stubbed-apis
docker compose up -d --quiet-pull --wait db keycloak
cd backend && MONITORFISH_OIDC_ENABLED=false ./gradlew bootRun --args='--spring.profiles.active=local --spring.config.additional-location=$(INFRA_FOLDER)'

run-back-with-monitorenv: run-monitorenv
docker compose up -d --quiet-pull --wait db
cd backend && MONITORENV_URL=http://localhost:9880 ./gradlew bootRun --args='--spring.profiles.active=local --spring.config.additional-location=$(INFRA_FOLDER)'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import fr.gouv.cnsp.monitorfish.domain.entities.gear.Gear
import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.*
import fr.gouv.cnsp.monitorfish.domain.entities.port.Port
import fr.gouv.cnsp.monitorfish.domain.entities.species.Species
import fr.gouv.cnsp.monitorfish.domain.exceptions.EntityConversionException
import org.slf4j.LoggerFactory
import java.time.ZonedDateTime

Expand Down Expand Up @@ -44,45 +43,6 @@ data class LogbookMessage(
) {
private val logger = LoggerFactory.getLogger(LogbookMessage::class.java)

/**
* Returns the reference logbook message `reportId` (= the original DAT operation `reportId`).
*/
fun getReferenceReportId(): String? {
return referencedReportId ?: reportId
}

fun <T : LogbookMessageValue> toConsolidatedLogbookMessageAndValue(
relatedLogbookMessages: List<LogbookMessage>,
clazz: Class<T>,
): LogbookMessageAndValue<T> {
if (reportId == null) {
throw EntityConversionException(
"Logbook report $id has no `reportId`. You can only enrich a DAT or an orphan COR operation with a `reportId`.",
)
}
if (operationType !in listOf(LogbookOperationType.DAT, LogbookOperationType.COR)) {
throw EntityConversionException(
"Logbook report $id has operationType '$operationType'. You can only enrich a DAT or an orphan COR operation.",
)
}

val historicallySortedRelatedLogbookMessages = relatedLogbookMessages.sortedBy { it.reportDateTime }
val maybeLastLogbookMessageCorrection = historicallySortedRelatedLogbookMessages
.lastOrNull { it.operationType == LogbookOperationType.COR }

val logbookMessageBase = maybeLastLogbookMessageCorrection ?: this
logbookMessageBase.enrichAcnkowledge(relatedLogbookMessages)
val finalLogbookMessage = logbookMessageBase.copy(
isCorrectedByNewerMessage = false,
isDeleted = historicallySortedRelatedLogbookMessages.any { it.operationType == LogbookOperationType.DEL },
)

return LogbookMessageAndValue(
logbookMessage = finalLogbookMessage,
clazz = clazz,
)
}

fun setAcknowledge(newLogbookMessageAcknowledgement: LogbookMessage) {
val currentAcknowledgement = this.acknowledgment
val newAcknowledgement = newLogbookMessageAcknowledgement.message as Acknowledgment
Expand Down Expand Up @@ -172,46 +132,6 @@ data class LogbookMessage(
}
}

private fun enrichAcnkowledge(relatedLogbookMessages: List<LogbookMessage>) {
if (this.transmissionFormat == LogbookTransmissionFormat.FLUX ||
LogbookSoftware.isVisioCapture(software)
) {
this.setAcknowledgeAsSuccessful()

return
}

val historycallyOrderedRetLogbookMessages = relatedLogbookMessages
.filter { it.operationType == LogbookOperationType.RET && it.referencedReportId == reportId }
.sortedBy { it.reportDateTime }

val maybeLastSuccessfulRetLogbookMessage = historycallyOrderedRetLogbookMessages.lastOrNull {
val message = it.message as Acknowledgment

message.returnStatus == RETReturnErrorCode.SUCCESS.number
}
// If there is at least one successful RET message, we consider the report as acknowledged
if (maybeLastSuccessfulRetLogbookMessage != null) {
val lastSucessfulRetMessage = maybeLastSuccessfulRetLogbookMessage.message as Acknowledgment
this.acknowledgment = lastSucessfulRetMessage.also {
it.dateTime = maybeLastSuccessfulRetLogbookMessage.reportDateTime
it.isSuccess = true
}

return
}

// Else we consider the last (failure) RET message as the final acknowledgement
val maybeLastRetLogbookMessage = historycallyOrderedRetLogbookMessages.lastOrNull()
if (maybeLastRetLogbookMessage != null) {
val lastRetMessage = maybeLastRetLogbookMessage.message as Acknowledgment
this.acknowledgment = lastRetMessage.also {
it.dateTime = maybeLastRetLogbookMessage.reportDateTime
it.isSuccess = lastRetMessage.returnStatus == RETReturnErrorCode.SUCCESS.number
}
}
}

private fun enrichAcknowledgeCorrectionAndDeletion(contextLogbookMessages: List<LogbookMessage>) {
val referenceLogbookMessage = findReferencedLogbookMessage(contextLogbookMessages)
val relatedLogbookMessages = filterRelatedLogbookMessages(contextLogbookMessages)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification

import fr.gouv.cnsp.monitorfish.domain.entities.facade.Seafront
import fr.gouv.cnsp.monitorfish.domain.entities.gear.Gear
import fr.gouv.cnsp.monitorfish.domain.entities.logbook.LogbookMessage
import fr.gouv.cnsp.monitorfish.domain.entities.logbook.LogbookMessageAndValue
import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.Acknowledgment
import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.PNO
import fr.gouv.cnsp.monitorfish.domain.entities.port.Port
import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType
import fr.gouv.cnsp.monitorfish.domain.entities.reporting.filters.ReportingFilter
import fr.gouv.cnsp.monitorfish.domain.entities.risk_factor.VesselRiskFactor
import fr.gouv.cnsp.monitorfish.domain.entities.species.Species
import fr.gouv.cnsp.monitorfish.domain.entities.vessel.UNKNOWN_VESSEL
import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel
import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendInternalErrorCode
import fr.gouv.cnsp.monitorfish.domain.exceptions.NoERSMessagesFound
Expand Down Expand Up @@ -136,20 +139,52 @@ data class PriorNotification(
reportingCount = currentReportings?.count() ?: 0
}

fun markAsAcknowledged() {
logbookMessageAndValue = LogbookMessageAndValue(
logbookMessageAndValue.logbookMessage.copy(acknowledgment = Acknowledgment(isSuccess = true)),
PNO::class.java,
)
}

companion object {
private val logger = LoggerFactory.getLogger(PriorNotification::class.java)

fun fromLogbookMessage(logbookMessage: LogbookMessage): PriorNotification {
val logbookMessageAndValue = LogbookMessageAndValue(
logbookMessage = logbookMessage,
clazz = PNO::class.java,
)

return PriorNotification(
reportId = logbookMessage.reportId,
createdAt = logbookMessage.operationDateTime,
didNotFishAfterZeroNotice = false,
isManuallyCreated = false,
logbookMessageAndValue = logbookMessageAndValue,
sentAt = logbookMessageAndValue.logbookMessage.reportDateTime,
updatedAt = logbookMessage.operationDateTime,

// These props need to be calculated in the use case
port = null,
reportingCount = null,
seafront = null,
// For practical reasons `vessel` can't be `null`, so we temporarily set it to "Navire inconnu"
vessel = UNKNOWN_VESSEL,
lastControlDateTime = null,
)
}

/**
* Next initial state of the prior notification once it will be created or updated.
*
* Used within the prior notification form to display the next state of the prior notification in real-time.
*/
fun getNextState(
isInverificationScope: Boolean,
isInVerificationScope: Boolean,
isPartOfControlUnitSubscriptions: Boolean,
): PriorNotificationState {
return when {
isInverificationScope -> PriorNotificationState.PENDING_VERIFICATION
isInVerificationScope -> PriorNotificationState.PENDING_VERIFICATION
isPartOfControlUnitSubscriptions -> PriorNotificationState.AUTO_SEND_REQUESTED
else -> PriorNotificationState.OUT_OF_VERIFICATION_SCOPE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import fr.gouv.cnsp.monitorfish.domain.exceptions.NoLogbookFishingTripFound
import java.time.ZonedDateTime

interface LogbookReportRepository {
fun findAllPriorNotifications(filter: PriorNotificationsFilter): List<PriorNotification>
fun findAllAcknowledgedPriorNotifications(filter: PriorNotificationsFilter): List<PriorNotification>

@Throws(NoLogbookFishingTripFound::class)
fun findLastTripBeforeDateTime(
Expand Down Expand Up @@ -46,7 +46,7 @@ interface LogbookReportRepository {
// Only used in tests
fun findById(id: Long): LogbookMessage

fun findPriorNotificationByReportId(reportId: String, operationDate: ZonedDateTime): PriorNotification?
fun findAcknowledgedPriorNotificationByReportId(reportId: String, operationDate: ZonedDateTime): PriorNotification?

fun findLastMessageDate(): ZonedDateTime

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class GetPriorNotification(
val priorNotification = if (isManuallyCreated) {
manualPriorNotificationRepository.findByReportId(reportId)
} else {
logbookReportRepository.findPriorNotificationByReportId(reportId, operationDate)
logbookReportRepository.findAcknowledgedPriorNotificationByReportId(reportId, operationDate)
}
?: throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND)

Expand All @@ -55,6 +55,7 @@ class GetPriorNotification(
} else {
null
}

false -> if (priorNotification.logbookMessageAndValue.logbookMessage.internalReferenceNumber != null) {
vesselRepository.findFirstByInternalReferenceNumber(
priorNotification.logbookMessageAndValue.logbookMessage.internalReferenceNumber!!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class GetPriorNotifications(
val allSpecies = speciesRepository.findAll()

val (automaticPriorNotifications, findAllPriorNotificationsTimeTaken) = measureTimedValue {
logbookReportRepository.findAllPriorNotifications(filter)
logbookReportRepository.findAllAcknowledgedPriorNotifications(filter)
}
logger.info(
"TIME_RECORD - 'logbookReportRepository.findAllPriorNotifications()' took $findAllPriorNotificationsTimeTaken.",
Expand All @@ -58,11 +58,8 @@ class GetPriorNotifications(

val incompletePriorNotifications = automaticPriorNotifications + manualPriorNotifications

val undeletedPriorNotifications = incompletePriorNotifications
.filter { !it.logbookMessageAndValue.logbookMessage.isDeleted }

val (priorNotifications, enrichedPriorNotificationsTimeTaken) = measureTimedValue {
undeletedPriorNotifications
incompletePriorNotifications
.map { priorNotification ->
priorNotification.enrich(allRiskFactors, allPorts, priorNotification.isManuallyCreated)
priorNotification.logbookMessageAndValue.logbookMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ class PriorNotificationDetailDataOutput(
) {
companion object {
fun fromPriorNotification(priorNotification: PriorNotification): PriorNotificationDetailDataOutput {
val reportId = requireNotNull(priorNotification.reportId) {
"`reportId` is null."
}

val isLessThanTwelveMetersVessel = requireNotNull(priorNotification.vessel) {
"`priorNotification.vessel` is null."
}.isLessThanTwelveMetersVessel()
val isVesselUnderCharter = requireNotNull(priorNotification.vessel) {
"`priorNotification.vessel` is null."
}.underCharter
val logbookMessage = priorNotification.logbookMessageAndValue.logbookMessage
val referenceReportId = requireNotNull(logbookMessage.getReferenceReportId()) {
"`logbookMessage.getReferenceReportId()` returned null."
}

val logbookMessageDataOutput = LogbookMessageDataOutput.fromLogbookMessage(logbookMessage)

return PriorNotificationDetailDataOutput(
reportId = referenceReportId,
reportId,
fingerprint = priorNotification.fingerprint,
isLessThanTwelveMetersVessel = isLessThanTwelveMetersVessel,
isManuallyCreated = priorNotification.isManuallyCreated,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import org.slf4j.LoggerFactory
import java.time.ZonedDateTime

data class PriorNotificationListItemDataOutput(
/** Reference logbook message (report) `reportId`. */
val id: String,
val reportId: String,
val acknowledgment: AcknowledgmentDataOutput?,
val createdAt: ZonedDateTime?,
val expectedArrivalDate: ZonedDateTime?,
Expand Down Expand Up @@ -51,13 +50,13 @@ data class PriorNotificationListItemDataOutput(
val logger: Logger = LoggerFactory.getLogger(PriorNotificationListItemDataOutput::class.java)

fun fromPriorNotification(priorNotification: PriorNotification): PriorNotificationListItemDataOutput? {
val logbookMessage = priorNotification.logbookMessageAndValue.logbookMessage
val referenceReportId = logbookMessage.getReferenceReportId()
if (referenceReportId == null) {
logger.warn("Prior notification has neither `reportId` nor `referencedReportId`: $priorNotification.")
if (priorNotification.reportId == null) {
logger.warn("Prior notification has no `reportId`: $priorNotification.")

return null
}

val logbookMessage = priorNotification.logbookMessageAndValue.logbookMessage
val message = priorNotification.logbookMessageAndValue.value

val acknowledgment = logbookMessage.acknowledgment?.let { AcknowledgmentDataOutput.fromAcknowledgment(it) }
Expand All @@ -73,7 +72,7 @@ data class PriorNotificationListItemDataOutput(
val vessel = requireNotNull(priorNotification.vessel) { "`vessel` is null." }

return PriorNotificationListItemDataOutput(
id = referenceReportId,
reportId = priorNotification.reportId,
acknowledgment = acknowledgment,
createdAt = priorNotification.createdAt,
expectedArrivalDate = message.predictedArrivalDatetimeUtc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.entities

import com.fasterxml.jackson.databind.ObjectMapper
import fr.gouv.cnsp.monitorfish.domain.entities.logbook.*
import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.PNO
import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotification
import fr.gouv.cnsp.monitorfish.domain.entities.vessel.UNKNOWN_VESSEL
import fr.gouv.cnsp.monitorfish.domain.mappers.ERSMapper.getERSMessageValueFromJSON
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType
import jakarta.persistence.*
Expand Down Expand Up @@ -140,35 +137,6 @@ data class LogbookReportEntity(
)
}

fun toPriorNotification(mapper: ObjectMapper, relatedModels: List<LogbookReportEntity>): PriorNotification {
val referenceLogbookMessage = toLogbookMessage(mapper)
val relatedLogbookMessages = relatedModels
.map { it.toLogbookMessage(mapper) }
.sortedBy { it.operationDateTime }
val consolidatedLogbookMessageAndValue = referenceLogbookMessage
.toConsolidatedLogbookMessageAndValue(relatedLogbookMessages, PNO::class.java)
val updatedAt = relatedLogbookMessages.lastOrNull()?.operationDateTime ?: operationDateTime.atZone(UTC)
// For practical reasons `vessel` can't be `null`, so we temporarily set it to "Navire inconnu"
val vessel = UNKNOWN_VESSEL

return PriorNotification(
reportId = reportId,
createdAt = operationDateTime.atZone(UTC),
didNotFishAfterZeroNotice = false,
isManuallyCreated = false,
logbookMessageAndValue = consolidatedLogbookMessageAndValue,
sentAt = consolidatedLogbookMessageAndValue.logbookMessage.reportDateTime,
updatedAt = updatedAt,

// These props need to be calculated in the use case
port = null,
reportingCount = null,
seafront = null,
vessel = vessel,
lastControlDateTime = null,
)
}

private fun <T> deserializeJSONList(
mapper: ObjectMapper,
json: String?,
Expand Down
Loading

0 comments on commit 6676dfa

Please sign in to comment.