Skip to content
Draft
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
@@ -0,0 +1,13 @@
package fr.gouv.dgampa.rapportnav.domain.entities.mission

import java.time.Instant

data class MissionDates(
val startDateTimeUtc: Instant?,
val endDateTimeUtc: Instant?
) {
/**
* Returns true if the mission is finished (end date is in the past).
*/
fun isMissionFinished(): Boolean = endDateTimeUtc?.isBefore(Instant.now()) ?: false
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions.Infracti
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.action.ActionType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.control.ControlType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.status.ActionStatusType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionDates
import fr.gouv.dgampa.rapportnav.domain.validation.EntityValidityValidator
import fr.gouv.dgampa.rapportnav.domain.validation.ValidateThrowsBeforeSave
import fr.gouv.dgampa.rapportnav.domain.validation.ValidateWhenMissionFinished
import java.time.Instant



abstract class MissionActionEntity(
override val missionId: Int,
override val actionType: ActionType,
Expand All @@ -39,13 +41,13 @@ abstract class MissionActionEntity(
* Unified validation method - validates both validity (dates) and completeness (required fields).
* Uses Jakarta Bean Validation with validation groups.
*
* @param isMissionFinished When true, also checks required fields (ValidateWhenMissionFinished group)
* @param validator The EntityValidityValidator instance to use
* @param missionDates Mission dates used for grandfathering rules by effective date
*/
open fun computeValidityForStats(isMissionFinished: Boolean = false, validator: EntityValidityValidator) {
open fun computeValidityForStats(validator: EntityValidityValidator, missionDates: MissionDates? = null) {
val groups = mutableListOf<Class<*>>(ValidateThrowsBeforeSave::class.java, ValidateWhenMissionFinished::class.java)

this.completenessForStats = validator.validate(this, *groups.toTypedArray())
this.completenessForStats = validator.validate(this, *groups.toTypedArray(), missionStartDate = missionDates?.startDateTimeUtc)

// Update sources of missing data based on completeness
if (this.completenessForStats?.status != CompletenessForStatsStatusEnum.VALID) {
Expand Down Expand Up @@ -139,10 +141,10 @@ abstract class MissionActionEntity(

/**
* Computes validity for statistics using the new unified validation system.
* @param isMissionFinished When true, also checks required fields (ValidateWhenMissionFinished group)
* @param validator The EntityValidityValidator instance to use
* @param missionDates Mission dates used for grandfathering rules by effective date
*/
abstract fun computeValidity(isMissionFinished: Boolean = false, validator: EntityValidityValidator)
abstract fun computeValidity(validator: EntityValidityValidator, missionDates: MissionDates? = null)

abstract fun isControlInValid(control: ControlEntity?): Boolean
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.tags.TagEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.themes.ThemeEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.action.ActionType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.control.ControlType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionDates
import fr.gouv.dgampa.rapportnav.domain.validation.*
import org.locationtech.jts.geom.Geometry
import java.time.Instant
Expand Down Expand Up @@ -89,10 +90,10 @@ data class MissionEnvActionEntity(
* Computes validity for statistics using the new unified validation system.
* For Env actions, validates both RAPPORT_NAV and MONITORENV sources.
*
* @param isMissionFinished When true, also checks required fields (ValidateWhenMissionFinished group)
* @param validator The EntityValidityValidator instance to use
* @param missionDates Mission dates used for grandfathering rules by effective date
*/
override fun computeValidity(isMissionFinished: Boolean, validator: EntityValidityValidator) {
override fun computeValidity(validator: EntityValidityValidator, missionDates: MissionDates?) {
this.computeControlsToComplete()
this.computeAvailableControlTypesForInfraction()

Expand All @@ -104,7 +105,8 @@ data class MissionEnvActionEntity(
this,
MissionSourceEnum.RAPPORT_NAV,
ValidateThrowsBeforeSave::class.java,
ValidateWhenMissionFinished::class.java
ValidateWhenMissionFinished::class.java,
missionStartDate = missionDates?.startDateTimeUtc
)

val rapportNavComplete = rapportNavCompleteness.isComplete && this.controlsToComplete.isNullOrEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.MissionSourceEnum
import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.ControlUnit
import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.*
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.action.ActionType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionDates
import fr.gouv.dgampa.rapportnav.domain.validation.*
import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity
import java.time.Instant
Expand Down Expand Up @@ -106,10 +107,10 @@ class MissionFishActionEntity(
* Computes validity for statistics using the new unified validation system.
* For Fish actions, validates both RAPPORT_NAV and MONITORFISH sources.
*
* @param isMissionFinished When true, also checks required fields (ValidateWhenMissionFinished group)
* @param validator The EntityValidityValidator instance to use
* @param missionDates Mission dates used for grandfathering rules by effective date
*/
override fun computeValidity(isMissionFinished: Boolean, validator: EntityValidityValidator) {
override fun computeValidity(validator: EntityValidityValidator, missionDates: MissionDates?) {
this.computeControlsToComplete()

val sourcesOfMissingData = mutableListOf<MissionSourceEnum>()
Expand All @@ -120,7 +121,8 @@ class MissionFishActionEntity(
this,
MissionSourceEnum.RAPPORT_NAV,
ValidateThrowsBeforeSave::class.java,
ValidateWhenMissionFinished::class.java
ValidateWhenMissionFinished::class.java,
missionStartDate = missionDates?.startDateTimeUtc
)

val rapportNavComplete = rapportNavCompleteness.isComplete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.control.ControlMeth
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.control.LocationType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.status.ActionStatusReason
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.status.ActionStatusType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionDates
import fr.gouv.dgampa.rapportnav.domain.validation.*
import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.action.v2.MissionActionModel
import jakarta.validation.constraints.Min
Expand Down Expand Up @@ -129,11 +130,11 @@ class MissionNavActionEntity(
* Computes validity for statistics using the new unified validation system.
* Uses Jakarta Bean Validation with validation groups.
*
* @param isMissionFinished When true, also checks required fields (ValidateWhenMissionFinished group)
* @param validator The EntityValidityValidator instance to use
* @param missionDates Mission dates used for grandfathering rules by effective date
*/
override fun computeValidity(isMissionFinished: Boolean, validator: EntityValidityValidator) {
this.computeValidityForStats(isMissionFinished, validator)
override fun computeValidity(validator: EntityValidityValidator, missionDates: MissionDates?) {
this.computeValidityForStats(validator, missionDates)

// For Nav actions, the only source is RAPPORT_NAV
if (this.completenessForStats?.isComplete != true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ class ProcessEnvAction(
action.status = this.getStatus(action)

val missionDates = getMissionDates.execute(missionId = missionId, ownerId = null)
val isMissionFinished = missionDates?.isMissionFinished() ?: false

action.computeValidity(isMissionFinished, entityValidityValidator)
action.computeValidity(validator = entityValidityValidator, missionDates = missionDates)
return action
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ class ProcessFishAction(
entity.status = this.getStatus(entity)

val missionDates = getMissionDates.execute(missionId = missionId, ownerId = null)
val isMissionFinished = missionDates?.isMissionFinished() ?: false

entity.computeValidity(isMissionFinished, entityValidityValidator)
entity.computeValidity(validator = entityValidityValidator, missionDates = missionDates)
return entity
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ class ProcessNavAction(
ownerId = action.ownerId,
inquiryId = if (action.actionType == ActionType.INQUIRY) action.ownerId else null
)
val isMissionFinished = missionDates?.isMissionFinished() ?: false
action.computeValidity(isMissionFinished = isMissionFinished, validator = entityValidityValidator)
action.computeValidity(validator = entityValidityValidator, missionDates = missionDates)

return action
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class UpdateEnvAction(
)
// compute validity
val missionDates = getMissionDates.execute(missionId = action.missionId, ownerId = null)
action.computeValidity(isMissionFinished = missionDates?.isMissionFinished() ?: false, validator = entityValidityValidator)
action.computeValidity(validator = entityValidityValidator, missionDates = missionDates)
return action
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class UpdateFishAction(
)
// compute validity
val missionDates = getMissionDates.execute(missionId = action.missionId, ownerId = null)
action.computeValidity(isMissionFinished = missionDates?.isMissionFinished() ?: false, validator = entityValidityValidator)
action.computeValidity(validator = entityValidityValidator, missionDates = missionDates)
return action
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ class UpdateNavAction(
ownerId = action.ownerId,
inquiryId = if (action.actionType == ActionType.INQUIRY) action.ownerId else null
)
val isMissionFinished = missionDates?.isMissionFinished() ?: false
action.computeValidity(isMissionFinished = isMissionFinished, validator = entityValidityValidator)
action.computeValidity(validator = entityValidityValidator, missionDates = missionDates)

return action
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
package fr.gouv.dgampa.rapportnav.domain.use_cases.mission.v2

import fr.gouv.dgampa.rapportnav.config.UseCase
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionDates
import fr.gouv.dgampa.rapportnav.domain.entities.mission.v2.InquiryEntity
import fr.gouv.dgampa.rapportnav.domain.use_cases.inquiry.GetInquiryById
import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action.v2.GetEnvMissionById2
import java.time.Instant
import java.util.*

data class MissionDatesOutput(
val startDateTimeUtc: Instant?,
val endDateTimeUtc: Instant?
) {
/**
* Returns true if the mission is finished (end date is in the past).
*/
fun isMissionFinished(): Boolean = endDateTimeUtc?.isBefore(Instant.now()) ?: false
}

@UseCase
class GetMissionDates(
private val getNavMissionById2: GetNavMissionById2,
private val getEnvMissionById2: GetEnvMissionById2,
private val getInquiryById: GetInquiryById
) {

fun execute(missionId: Int?, ownerId: UUID?, inquiryId: UUID? = null): MissionDatesOutput? {
fun execute(missionId: Int?, ownerId: UUID?, inquiryId: UUID? = null): MissionDates? {

// Try to get from Inquiry table if inquiryId is provided
if (inquiryId != null) {
val inquiry: InquiryEntity? = inquiryId.let { getInquiryById.execute(id = it) }
if (inquiry != null) {
return MissionDatesOutput(
return MissionDates(
startDateTimeUtc = inquiry.startDateTimeUtc,
endDateTimeUtc = inquiry.endDateTimeUtc
)
Expand All @@ -41,7 +31,7 @@ class GetMissionDates(
if (ownerId != null) {
val navMission = getNavMissionById2.execute(ownerId)
if (navMission != null) {
return MissionDatesOutput(
return MissionDates(
startDateTimeUtc = navMission.startDateTimeUtc,
endDateTimeUtc = navMission.endDateTimeUtc
)
Expand All @@ -52,7 +42,7 @@ class GetMissionDates(
if (missionId != null) {
val envMission = getEnvMissionById2.execute(missionId)
if (envMission != null) {
return MissionDatesOutput(
return MissionDates(
startDateTimeUtc = envMission.startDateTimeUtc,
endDateTimeUtc = envMission.endDateTimeUtc
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import fr.gouv.dgampa.rapportnav.domain.exceptions.BackendUsageException
import jakarta.validation.Validation
import jakarta.validation.Validator
import org.springframework.stereotype.Service
import java.time.Instant

/**
* Spring service wrapper around Jakarta Bean Validation for validating entities.
Expand All @@ -25,8 +26,13 @@ class EntityValidityValidator(
* @param groups Validation groups to run (e.g., ValidateThrowsBeforeSave, ValidateWhenMissionFinished)
* @return CompletenessForStatsEntity with status and any errors
*/
fun validate(entity: Any, vararg groups: Class<*>): CompletenessForStatsEntity {
val violations = validator.validate(entity, *groups).toSet()
fun validate(entity: Any, vararg groups: Class<*>, missionStartDate: Instant? = null): CompletenessForStatsEntity {
RequiredFieldsValidator.missionStartDateHolder.set(missionStartDate)
val violations = try {
validator.validate(entity, *groups).toSet()
} finally {
RequiredFieldsValidator.missionStartDateHolder.remove()
}

if (violations.isEmpty()) {
return CompletenessForStatsEntity(
Expand Down Expand Up @@ -68,8 +74,8 @@ class EntityValidityValidator(
* @param groups Validation groups to run
* @throws BackendUsageException if validation fails
*/
fun validateAndThrow(entity: Any, vararg groups: Class<*>) {
val result = validate(entity, *groups)
fun validateAndThrow(entity: Any, vararg groups: Class<*>, missionStartDate: Instant? = null) {
val result = validate(entity, *groups, missionStartDate = missionStartDate)
if (!result.isComplete) {
throw BackendUsageException(
code = BackendUsageErrorCode.INVALID_PARAMETERS_EXCEPTION,
Expand All @@ -96,9 +102,10 @@ class EntityValidityValidator(
fun validateWithSource(
entity: Any,
source: MissionSourceEnum,
vararg groups: Class<*>
vararg groups: Class<*>,
missionStartDate: Instant? = null
): CompletenessForStatsEntity {
val result = validate(entity, *groups)
val result = validate(entity, *groups, missionStartDate = missionStartDate)
return if (result.status != CompletenessForStatsStatusEnum.VALID) {
result.copy(sources = listOf(source))
} else {
Expand Down
Loading
Loading