From 13a93ea6f9ecbc6f0e589bf3ba9111bfe76410ad Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 25 Jun 2026 09:54:51 +0200 Subject: [PATCH 1/4] feat(backend): update Sati - Change Database structure, Model, entity - Add Monitor attributes - Implement SatiEntityMapper - getComputeSati use case which getSait or Create if is not iexisting - processAti save sati if changes --- .../controlResources/ControlResourceEntity.kt | 8 +- .../entities/mission/fish/ControlResource.kt | 6 +- .../fish/fishActions/LogbookMessagePurpose.kt | 36 ++ .../mission/fish/fishActions/MissionAction.kt | 17 +- .../entities/mission/sati/AddressEntity.kt | 11 +- .../entities/mission/sati/AuthorityType.kt | 7 + .../entities/mission/sati/ContactEntity.kt | 11 +- .../entities/mission/sati/SatiEntity.kt | 77 +-- .../entities/mission/sati/SatiEntityMapper.kt | 57 +- .../mission/sati/SatiInspectorEntity.kt | 14 + .../entities/mission/sati/SatiJpeEntity.kt | 13 + .../entities/mission/sati/SatiModuleType.kt | 22 + .../entities/mission/sati/SatiPartyEntity.kt | 13 + .../entities/mission/sati/SatiVesselEntity.kt | 26 + .../mission/action/v2/GetComputeSati.kt | 35 +- .../api/bff/model/sati/Address.kt | 8 +- .../api/bff/model/sati/Contact.kt | 13 +- .../infrastructure/api/bff/model/sati/Sati.kt | 75 +-- .../api/bff/model/sati/SatiInspector.kt | 11 + .../api/bff/model/sati/SatiJpe.kt | 13 + .../api/bff/model/sati/SatiMapper.kt | 344 ++++++------ .../api/bff/model/sati/SatiParty.kt | 11 + .../api/bff/model/sati/SatiVessel.kt | 23 + .../api/bff/model/v2/MissionFishAction.kt | 4 +- .../model/mission/sati/AddressModel.kt | 66 +-- .../model/mission/sati/ContactModel.kt | 35 +- .../model/mission/sati/SatiInspectorModel.kt | 48 ++ .../database/model/mission/sati/SatiModel.kt | 235 ++------ .../model/mission/sati/SatiModelMapper.kt | 269 +++++---- .../model/mission/sati/SatiPartyModel.kt | 48 ++ .../model/mission/sati/SatiVesselModel.kt | 43 ++ .../mission/sati/JPASatiRepository.kt | 1 + .../output/MissionActionDataOutput.kt | 32 +- ...__update_sati_table_and_correspondance.sql | 92 ++++ .../mission/sati/SatiEntityMapperTest.kt | 302 +++++++++++ .../mission/sati/GetComputeSatiTest.kt | 16 +- .../use_cases/mission/sati/ProcessSatiTest.kt | 17 +- .../api/model/sati/SatiMapperTest.kt | 512 ++++++++++++------ .../api/model/sati/SatiModelMapperTest.kt | 510 +++++++++++------ .../mission/sati/JPASatiRepositoryTest.kt | 9 +- 40 files changed, 2012 insertions(+), 1078 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/LogbookMessagePurpose.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AuthorityType.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiInspectorEntity.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiJpeEntity.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiModuleType.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiPartyEntity.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiInspector.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiJpe.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiParty.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiVessel.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt create mode 100644 backend/src/main/resources/db/migration/V1.2026.06.22.15.55__update_sati_table_and_correspondance.sql create mode 100644 backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/env/controlResources/ControlResourceEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/env/controlResources/ControlResourceEntity.kt index eb8a02e4e..fe9cb5a57 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/env/controlResources/ControlResourceEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/env/controlResources/ControlResourceEntity.kt @@ -3,9 +3,11 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.env.ControlUnitResourceEnv data class ControlResourceEntity( - val id: Int, - val name: String, - val type: ControlUnitResourceType + val id: Int? = null, + val name: String? = null, + val type: ControlUnitResourceType? = null, + val registrationId: String? = null, + val radioFrequency: String? = null ) { companion object { fun fromControlUnitResourceEnv( diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/ControlResource.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/ControlResource.kt index aab2216b0..c6fd9e56e 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/ControlResource.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/ControlResource.kt @@ -1,6 +1,8 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.fish data class ControlResource( - val id: Int, - val name: String, + val id: Int? = null, + val name: String? = null, + val registrationId: String? = null, + val radioFrequency: String? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/LogbookMessagePurpose.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/LogbookMessagePurpose.kt new file mode 100644 index 000000000..53d83c570 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/LogbookMessagePurpose.kt @@ -0,0 +1,36 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions + +enum class LogbookMessagePurpose { + // TODO Find out what this purpose code means. + ACS, + + /** Emergency. */ + ECY, + + /** Vessels grounded and called by the authorities. */ + GRD, + + /** Landing. */ + LAN, + + /** Other. */ + OTH, + + /** Refueling. */ + REF, + + /** Repair. */ + REP, + + /** Rest. */ + RES, + + /** Return for Scientific Research. */ + SCR, + + /** Sheltering. */ + SHE, + + /** Transhipment. */ + TRA, +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/MissionAction.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/MissionAction.kt index 8e38866e3..20d261fa8 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/MissionAction.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/fish/fishActions/MissionAction.kt @@ -4,6 +4,7 @@ import com.neovisionaries.i18n.CountryCode import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.ControlUnit import java.time.Instant + typealias FishAction = MissionAction data class MissionAction( @@ -66,5 +67,19 @@ data class MissionAction( var speciesQuantitySeized: Int ? = null, val infractions: List = listOf(), val hasDivingDuringOperation: Boolean? = null, - val incidentDuringOperation: Boolean? = null + val incidentDuringOperation: Boolean? = null, + val vesselLength: Double? = null, + val vesselType: String? = null, + val imo: String? = null, + val proprietorName: String? = null, + val proprietorPhones: List? = listOf(), + val proprietorEmails: List? = listOf(), + val proprietorNationality: String? = null, + val proprietorAddress: String? = null, + val tripNumber: String? = null, + val pnoReportId: String? = null, + val pnoPurpose: LogbookMessagePurpose? = null, + val lastDeparturePortLocode: String? = null, + val lastDeparturePortName: String? = null, + val lastDepartureDateTime: Instant? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AddressEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AddressEntity.kt index a9f0d62ae..ca15584d4 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AddressEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AddressEntity.kt @@ -2,15 +2,16 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati import com.neovisionaries.i18n.CountryCode import java.time.Instant -import java.util.UUID data class AddressEntity( - val id: UUID? = null, + val id: Int? = null, val street: String? = null, + val fullAddress: String? = null, val zipcode: String? = null, val town: String? = null, - val lat: Double? = null, - val lng: Double? = null, val country: CountryCode? = null, - val createdAt: Instant? = null + val lng: Double? = null, + val lat: Double? = null, + val createdAt: Instant? = null, + val updatedAt: Instant? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AuthorityType.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AuthorityType.kt new file mode 100644 index 000000000..a999915c7 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/AuthorityType.kt @@ -0,0 +1,7 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati + +enum class AuthorityType { + AECP, + OTHERS, + MEMBER_FR +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/ContactEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/ContactEntity.kt index 5c7a7da99..142f01532 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/ContactEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/ContactEntity.kt @@ -1,15 +1,16 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati -import com.neovisionaries.i18n.CountryCode import java.time.Instant -import java.util.UUID data class ContactEntity( - val id: UUID? = null, + val id: Int? = null, val fullName: String? = null, - val nationality: CountryCode? = null, + val firstName: String? = null, + val lastName: String? = null, + val nationality: String? = null, val email: String? = null, val phone: String? = null, val address: AddressEntity? = null, - val createdAt: Instant? = null + val createdAt: Instant? = null, + val updatedAt: Instant? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt index b10f728a5..31d3c54d2 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt @@ -1,69 +1,20 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati -import com.neovisionaries.i18n.CountryCode +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity import java.time.Instant -import java.util.UUID +import java.util.* -data class SatiEntity( - val id: UUID? = null, - val module: String, - val actionId: String, - val createdAt: Instant? = null, - val updatedAt: Instant? = null, - val actionTaken: String? = null, - val agentContact: ContactEntity? = null, - val appointingAuthority: String? = null, - val beneficialOwnerContact: ContactEntity? = null, - val catchCertificateInfo: Boolean? = null, - val chartererContact: ContactEntity? = null, - val commonMarketingStandards: Boolean? = null, - val driverContact: ContactEntity? = null, - val euFishingTripId: String? = null, - val fisheryLotLabelling: Boolean? = null, - val fisheryProductDocumentsInspection: Boolean? = null, - val fisheryProductStorageMechanismInspected: Boolean? = null, - val fisheryProductWeighedBeforeSale: Boolean? = null, - val freshnessCategories: String? = null, - val importerContact: ContactEntity? = null, - val infringementsObservations: String? = null, - val inspectionEndDatetimeUtc: Instant? = null, - val inspectionStartDatetimeUtc: Instant, - val inspectionLocationAddressId: String? = null, - val inspectorComments: String? = null, - val inspectorNameOrId: String? = null, - val inspectorSignature: Boolean? = null, - val inspectorUniqueId: String? = null, - val landingDeclarationInfo: Boolean? = null, - val lotIdSpeciesQuantities: String? = null, - val lotsArt58Compliance: Boolean? = null, - val marketOwnerContact: ContactEntity? = null, - val marketOwnerRepresentativeContact: ContactEntity? = null, - val marketPremisesAddressId: String? = null, - val marketPremisesLocation: String? = null, - val marketPremisesName: String? = null, - val masterComments: String? = null, - val masterContact: ContactEntity? = null, - val newLotCompositionInfo: String? = null, - val operatorComments: String? = null, - val operatorSignature: Boolean? = null, - val patrolVesselExternalMarking: String? = null, - val patrolVesselRadioCallSign: String? = null, - val registeredBuyerContact: ContactEntity? = null, - val responsiblePersonSignature: Boolean? = null, - val sizeGradingCategories: String? = null, - val supplierInvoiceInfo: Boolean? = null, - val takeOverDeclarationInfo: Boolean? = null, - val traceabilityRecordingSystemArt58: Boolean? = null, - val traceabilitySupplierIdentificationSystem: Boolean? = null, - val tractorRegistrationNumber: String? = null, - val trailerRegistrationNumber: String? = null, - val transportDocumentInfo: Boolean? = null, - val transporterComments: String? = null, - val transporterSignature: Boolean? = null, - val useOfUndersizedFisheryProducts: Boolean? = null, - val vehicleNationality: CountryCode? = null, - val vehicleOwnerContact: ContactEntity? = null, - val vehicleType: String? = null, - val vesselOwnerContact: ContactEntity? = null +data class SatiEntity( + var id: UUID? = null, + var module: SatiModuleType, + var actionId: String, + var jpe: SatiJpeEntity? = null, + var resource: ControlResourceEntity? = null, + var vessel: SatiVesselEntity? = null, + var createdAt: Instant? = null, + var updatedAt: Instant? = null, + var startDatetimeUtc: Instant? = null, + var endDatetimeUtc: Instant? = null, + var inspectors: List? = emptyList(), ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt index 308323882..ec0071b36 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt @@ -1,12 +1,63 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati +import com.neovisionaries.i18n.CountryCode +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlUnitResourceType +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.LogbookMessagePurpose import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionAction import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Sati -import fr.gouv.dgampa.rapportnav.infrastructure.database.repositories.mission.sati.SatiModelMapper.toModel +import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.SatiModelMapper.toModel +import java.time.Instant +import java.util.UUID object SatiEntityMapper { - fun merge(sati: SatiEntity?, action: MissionAction): SatiEntity? { - return sati + fun merge(sati: SatiEntity, action: MissionAction): SatiEntity { + return SatiEntity( + id = sati.id, + module = sati.module, + actionId = action.id?.toString() ?: "", + createdAt = sati.createdAt, + updatedAt = sati.updatedAt, + vessel = SatiVesselEntity( + jpe = SatiJpeEntity( + pnoId = action.pnoReportId, + portName = action.lastDeparturePortName, + portId = action.lastDeparturePortLocode, + lastStopDate = action.lastDepartureDateTime, + tripNumber = action.tripNumber ?: sati.vessel?.tripNumber, + pnoType = action.pnoPurpose ?: sati.vessel?.pnoType?.let { LogbookMessagePurpose.valueOf(it) }, + ), + ircs = action.ircs, + imo = action.imo, + type = action.vesselType, + name = action.vesselName, + length = action.vesselLength, + immat = action.internalReferenceNumber, + extRef = action.externalReferenceNumber, + owner = SatiPartyEntity( + contact = ContactEntity( + id = null, + createdAt = null, + fullName = action.proprietorName, + email = action.proprietorEmails?.firstOrNull(), + phone = action.proprietorPhones?.firstOrNull(), + nationality = action.proprietorNationality, + address = AddressEntity( + fullAddress = action.proprietorAddress + ), + ) + + ), + charterer = null, + agent = sati.vessel?.agent, + master = sati.vessel?.master, + flagState = action.flagState, + ), + resource = sati.resource, + inspectors = sati.inspectors, + startDatetimeUtc = action.actionDatetimeUtc, + endDatetimeUtc = action.actionEndDatetimeUtc + ) } fun merge(sati: SatiEntity?, input: Sati): SatiEntity? { diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiInspectorEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiInspectorEntity.kt new file mode 100644 index 000000000..25c8e9469 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiInspectorEntity.kt @@ -0,0 +1,14 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati + +import java.time.Instant + +data class SatiInspectorEntity( + val id: Int? = null, + val agentId: Int? = null, + val party: SatiPartyEntity? = null, + val authorityType: AuthorityType? = null, + val externalAgentId: String? = null, + val isOutOfUnit: Boolean = false, + val createdAt: Instant? = null, + val updatedAt: Instant? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiJpeEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiJpeEntity.kt new file mode 100644 index 000000000..87f9cbad8 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiJpeEntity.kt @@ -0,0 +1,13 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati + +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.LogbookMessagePurpose +import java.time.Instant + +data class SatiJpeEntity( + val pnoId: String? = null, + val portId: String? = null, + val portName: String? = null, + val tripNumber: String? = null, + val lastStopDate: Instant? = null, + val pnoType: LogbookMessagePurpose? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiModuleType.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiModuleType.kt new file mode 100644 index 000000000..b32f1618c --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiModuleType.kt @@ -0,0 +1,22 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati + +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionActionType + + +enum class SatiModuleType { + M1, + M3, + M5, + M6, + M7; + + companion object { + fun fromMissionActionType(type: MissionActionType): SatiModuleType = when (type) { + MissionActionType.SEA_CONTROL -> M1 + MissionActionType.LAND_CONTROL -> M3 + MissionActionType.AIR_CONTROL -> M5 + MissionActionType.AIR_SURVEILLANCE -> M6 + MissionActionType.OBSERVATION -> M7 + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiPartyEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiPartyEntity.kt new file mode 100644 index 000000000..fe49f52c2 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiPartyEntity.kt @@ -0,0 +1,13 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati + +import java.time.Instant + +data class SatiPartyEntity( + val id: Int? = null, + val partyType: String? = null, + val comments: String? = null, + val signature: Boolean = false, + val contact: ContactEntity? = null, + val createdAt: Instant? = null, + val updatedAt: Instant? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt new file mode 100644 index 000000000..5950c4f3e --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt @@ -0,0 +1,26 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati + +import com.neovisionaries.i18n.CountryCode +import java.time.Instant + +data class SatiVesselEntity( + val id: Int? = null, + val jpe: SatiJpeEntity? = null, + val type: String? = null, + val name: String? = null, + val immat: String? = null, + val imo: String? = null, + val length: Double? = null, + val extRef: String? = null, + val ircs: String? = null, + val owner: SatiPartyEntity? = null, + val flagState: CountryCode? = null, + val charterer: SatiPartyEntity? = null, + val pnoType: String? = null, + val tripNumber: String? = null, + val agent: SatiPartyEntity? = null, + val master: SatiPartyEntity? = null, + val isMasterOwner: Boolean = false, + val createdAt: Instant? = null, + val updatedAt: Instant? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt index c1302b61e..085eb3d8c 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt @@ -1,19 +1,46 @@ package fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action.v2 import fr.gouv.dgampa.rapportnav.config.UseCase +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionAction -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntityMapper +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* import fr.gouv.dgampa.rapportnav.domain.repositories.mission.sati.ISatiRepository +import fr.gouv.dgampa.rapportnav.domain.repositories.v2.controlUnitResource.IEnvControlUnitResourceRepository @UseCase class GetComputeSati( - private val satiRepo: ISatiRepository + private val satiRepo: ISatiRepository, + private val controlResourceRepo: IEnvControlUnitResourceRepository ) { fun execute(action: MissionAction): SatiEntity? { if (action.id == null) throw IllegalArgumentException() if (!action.actionType.toString().endsWith("_CONTROL")) return null - val sati = satiRepo.findByActionId(actionId = action.id.toString()) + var sati = satiRepo.findByActionId(actionId = action.id.toString()) + if (sati == null) sati = satiRepo.save(getNewSati(action = action)) + + sati.resource = getControlResource(sati.resource?.id) return SatiEntityMapper.merge(sati = sati, action = action) } + + private fun getControlResource(resourceId: Int?): ControlResourceEntity? { + return controlResourceRepo.findAll().find { it.id == resourceId } + ?.let { + ControlResourceEntity( + id = it.id, + name = it.name, + type = it.type, + registrationId = it.registrationId, + radioFrequency = it.radioFrequency + ) + } + } + + private fun getNewSati(action: MissionAction): SatiEntity { + return SatiEntity( + vessel = SatiVesselEntity(), + actionId = action.id.toString(), + module = SatiModuleType.fromMissionActionType(action.actionType), + inspectors = listOf(SatiInspectorEntity(party = SatiPartyEntity())) + ) + } } diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Address.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Address.kt index 09d8acb80..f249a8840 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Address.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Address.kt @@ -1,16 +1,14 @@ package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati import com.neovisionaries.i18n.CountryCode -import java.time.Instant -import java.util.UUID data class Address( - val id: UUID? = null, + val id: Int? = null, + val fullAddress: String? = null, val street: String? = null, val zipcode: String? = null, val town: String? = null, val country: CountryCode? = null, val lat: Double? = null, - val lng: Double? = null, - val createdAt: Instant? = null + val lng: Double? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Contact.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Contact.kt index 755a12d77..bc98ea39c 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Contact.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Contact.kt @@ -1,15 +1,12 @@ package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati -import com.neovisionaries.i18n.CountryCode -import java.time.Instant -import java.util.UUID - data class Contact( - val id: UUID? = null, + val id: Int? = null, val fullName: String? = null, - val nationality: CountryCode? = null, + val firstName: String? = null, + val lastName: String? = null, + val nationality: String? = null, val email: String? = null, val phone: String? = null, - val address: Address? = null, - val createdAt: Instant? = null + val address: Address? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Sati.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Sati.kt index 1cbc28c0e..e8df613c6 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Sati.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/Sati.kt @@ -1,68 +1,19 @@ package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati -import com.neovisionaries.i18n.CountryCode +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.ControlResource +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiModuleType +import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.SatiInspector +import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.SatiVessel import java.time.Instant -import java.util.UUID +import java.util.* data class Sati( - val id: UUID? = null, - val module: String, - val actionId: String, - val createdAt: Instant? = null, - val updatedAt: Instant? = null, - val actionTaken: String? = null, - val agentContact: Contact? = null, - val appointingAuthority: String? = null, - val beneficialOwnerContact: Contact? = null, - val catchCertificateInfo: Boolean? = null, - val chartererContact: Contact? = null, - val commonMarketingStandards: Boolean? = null, - val driverContact: Contact? = null, - val euFishingTripId: String? = null, - val fisheryLotLabelling: Boolean? = null, - val fisheryProductDocumentsInspection: Boolean? = null, - val fisheryProductStorageMechanismInspected: Boolean? = null, - val fisheryProductWeighedBeforeSale: Boolean? = null, - val freshnessCategories: String? = null, - val importerContact: Contact? = null, - val infringementsObservations: String? = null, - val inspectionEndDatetimeUtc: Instant? = null, - val inspectionStartDatetimeUtc: Instant, - val inspectionLocationAddressId: String? = null, - val inspectorComments: String? = null, - val inspectorNameOrId: String? = null, - val inspectorSignature: Boolean? = null, - val inspectorUniqueId: String? = null, - val landingDeclarationInfo: Boolean? = null, - val lotIdSpeciesQuantities: String? = null, - val lotsArt58Compliance: Boolean? = null, - val marketOwnerContact: Contact? = null, - val marketOwnerRepresentativeContact: Contact? = null, - val marketPremisesAddressId: String? = null, - val marketPremisesLocation: String? = null, - val marketPremisesName: String? = null, - val masterComments: String? = null, - val masterContact: Contact? = null, - val newLotCompositionInfo: String? = null, - val operatorComments: String? = null, - val operatorSignature: Boolean? = null, - val patrolVesselExternalMarking: String? = null, - val patrolVesselRadioCallSign: String? = null, - val registeredBuyerContact: Contact? = null, - val responsiblePersonSignature: Boolean? = null, - val sizeGradingCategories: String? = null, - val supplierInvoiceInfo: Boolean? = null, - val takeOverDeclarationInfo: Boolean? = null, - val traceabilityRecordingSystemArt58: Boolean? = null, - val traceabilitySupplierIdentificationSystem: Boolean? = null, - val tractorRegistrationNumber: String? = null, - val trailerRegistrationNumber: String? = null, - val transportDocumentInfo: Boolean? = null, - val transporterComments: String? = null, - val transporterSignature: Boolean? = null, - val useOfUndersizedFisheryProducts: Boolean? = null, - val vehicleNationality: CountryCode? = null, - val vehicleOwnerContact: Contact? = null, - val vehicleType: String? = null, - val vesselOwnerContact: Contact? = null + var id: UUID? = null, + var actionId: String, + var module: SatiModuleType, + var resource: ControlResource? = null, + var vessel: SatiVessel? = null, + var startDatetimeUtc: Instant? = null, + var endDatetimeUtc: Instant? = null, + var inspectors: List? = emptyList(), ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiInspector.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiInspector.kt new file mode 100644 index 000000000..f3e6416aa --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiInspector.kt @@ -0,0 +1,11 @@ +package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati + +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.AuthorityType + +data class SatiInspector( + val id: Int? = null, + val party: SatiParty? = null, + val authorityType: AuthorityType? = null, + val agentId: Int? = null, + val isOutOfUnit: Boolean = false +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiJpe.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiJpe.kt new file mode 100644 index 000000000..244a34c2b --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiJpe.kt @@ -0,0 +1,13 @@ +package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati + +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.LogbookMessagePurpose +import java.time.Instant + +class SatiJpe( + val pnoId: String? = null, + val portId: String? = null, + val portName: String? = null, + val tripNumber: String? = null, + val lastStopDate: Instant? = null, + val pnoType: LogbookMessagePurpose? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt index e03175a92..867a81a92 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt @@ -1,190 +1,214 @@ package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.AddressEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.ContactEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.ControlResource +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Address import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Contact import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Sati object SatiMapper { - fun fromEntity(entity: SatiEntity): Sati { + fun fromEntity(entity: SatiEntity?): Sati { return Sati( - id = entity.id, - module = entity.module, - actionId = entity.actionId, - createdAt = entity.createdAt, - updatedAt = entity.updatedAt, - actionTaken = entity.actionTaken, - agentContact = entity.agentContact?.let { fromEntity(it) }, - appointingAuthority = entity.appointingAuthority, - beneficialOwnerContact = entity.beneficialOwnerContact?.let { fromEntity(it) }, - catchCertificateInfo = entity.catchCertificateInfo, - chartererContact = entity.chartererContact?.let { fromEntity(it) }, - commonMarketingStandards = entity.commonMarketingStandards, - driverContact = entity.driverContact?.let { fromEntity(it) }, - euFishingTripId = entity.euFishingTripId, - fisheryLotLabelling = entity.fisheryLotLabelling, - fisheryProductDocumentsInspection = entity.fisheryProductDocumentsInspection, - fisheryProductStorageMechanismInspected = entity.fisheryProductStorageMechanismInspected, - fisheryProductWeighedBeforeSale = entity.fisheryProductWeighedBeforeSale, - freshnessCategories = entity.freshnessCategories, - importerContact = entity.importerContact?.let { fromEntity(it) }, - infringementsObservations = entity.infringementsObservations, - inspectionEndDatetimeUtc = entity.inspectionEndDatetimeUtc, - inspectionStartDatetimeUtc = entity.inspectionStartDatetimeUtc, - inspectionLocationAddressId = entity.inspectionLocationAddressId, - inspectorComments = entity.inspectorComments, - inspectorNameOrId = entity.inspectorNameOrId, - inspectorSignature = entity.inspectorSignature, - inspectorUniqueId = entity.inspectorUniqueId, - landingDeclarationInfo = entity.landingDeclarationInfo, - lotIdSpeciesQuantities = entity.lotIdSpeciesQuantities, - lotsArt58Compliance = entity.lotsArt58Compliance, - marketOwnerContact = entity.marketOwnerContact?.let { fromEntity(it) }, - marketOwnerRepresentativeContact = entity.marketOwnerRepresentativeContact?.let { fromEntity(it) }, - marketPremisesAddressId = entity.marketPremisesAddressId, - marketPremisesLocation = entity.marketPremisesLocation, - marketPremisesName = entity.marketPremisesName, - masterComments = entity.masterComments, - masterContact = entity.masterContact?.let { fromEntity(it) }, - newLotCompositionInfo = entity.newLotCompositionInfo, - operatorComments = entity.operatorComments, - operatorSignature = entity.operatorSignature, - patrolVesselExternalMarking = entity.patrolVesselExternalMarking, - patrolVesselRadioCallSign = entity.patrolVesselRadioCallSign, - registeredBuyerContact = entity.registeredBuyerContact?.let { fromEntity(it) }, - responsiblePersonSignature = entity.responsiblePersonSignature, - sizeGradingCategories = entity.sizeGradingCategories, - supplierInvoiceInfo = entity.supplierInvoiceInfo, - takeOverDeclarationInfo = entity.takeOverDeclarationInfo, - traceabilityRecordingSystemArt58 = entity.traceabilityRecordingSystemArt58, - traceabilitySupplierIdentificationSystem = entity.traceabilitySupplierIdentificationSystem, - tractorRegistrationNumber = entity.tractorRegistrationNumber, - trailerRegistrationNumber = entity.trailerRegistrationNumber, - transportDocumentInfo = entity.transportDocumentInfo, - transporterComments = entity.transporterComments, - transporterSignature = entity.transporterSignature, - useOfUndersizedFisheryProducts = entity.useOfUndersizedFisheryProducts, - vehicleNationality = entity.vehicleNationality, - vehicleOwnerContact = entity.vehicleOwnerContact?.let { fromEntity(it) }, - vehicleType = entity.vehicleType, - vesselOwnerContact = entity.vesselOwnerContact?.let { fromEntity(it) } + id = entity?.id, + vessel = entity?.vessel?.toOutput(), + actionId = entity?.actionId.orEmpty(), + resource = entity?.resource?.toOutput(), + endDatetimeUtc = entity?.endDatetimeUtc, + startDatetimeUtc = entity?.startDatetimeUtc, + module = entity?.module?: SatiModuleType.M1, + inspectors = entity?.inspectors?.map { it.toOutput() } ?: listOf() ) } - fun toEntity(response: Sati): SatiEntity { + fun toEntity(response: Sati?): SatiEntity { return SatiEntity( - id = response.id, - module = response.module, - actionId = response.actionId, - createdAt = response.createdAt, - updatedAt = response.updatedAt, - actionTaken = response.actionTaken, - agentContact = response.agentContact?.let { toEntity(it) }, - appointingAuthority = response.appointingAuthority, - beneficialOwnerContact = response.beneficialOwnerContact?.let { toEntity(it) }, - catchCertificateInfo = response.catchCertificateInfo, - chartererContact = response.chartererContact?.let { toEntity(it) }, - commonMarketingStandards = response.commonMarketingStandards, - driverContact = response.driverContact?.let { toEntity(it) }, - euFishingTripId = response.euFishingTripId, - fisheryLotLabelling = response.fisheryLotLabelling, - fisheryProductDocumentsInspection = response.fisheryProductDocumentsInspection, - fisheryProductStorageMechanismInspected = response.fisheryProductStorageMechanismInspected, - fisheryProductWeighedBeforeSale = response.fisheryProductWeighedBeforeSale, - freshnessCategories = response.freshnessCategories, - importerContact = response.importerContact?.let { toEntity(it) }, - infringementsObservations = response.infringementsObservations, - inspectionEndDatetimeUtc = response.inspectionEndDatetimeUtc, - inspectionStartDatetimeUtc = response.inspectionStartDatetimeUtc, - inspectionLocationAddressId = response.inspectionLocationAddressId, - inspectorComments = response.inspectorComments, - inspectorNameOrId = response.inspectorNameOrId, - inspectorSignature = response.inspectorSignature, - inspectorUniqueId = response.inspectorUniqueId, - landingDeclarationInfo = response.landingDeclarationInfo, - lotIdSpeciesQuantities = response.lotIdSpeciesQuantities, - lotsArt58Compliance = response.lotsArt58Compliance, - marketOwnerContact = response.marketOwnerContact?.let { toEntity(it) }, - marketOwnerRepresentativeContact = response.marketOwnerRepresentativeContact?.let { toEntity(it) }, - marketPremisesAddressId = response.marketPremisesAddressId, - marketPremisesLocation = response.marketPremisesLocation, - marketPremisesName = response.marketPremisesName, - masterComments = response.masterComments, - masterContact = response.masterContact?.let { toEntity(it) }, - newLotCompositionInfo = response.newLotCompositionInfo, - operatorComments = response.operatorComments, - operatorSignature = response.operatorSignature, - patrolVesselExternalMarking = response.patrolVesselExternalMarking, - patrolVesselRadioCallSign = response.patrolVesselRadioCallSign, - registeredBuyerContact = response.registeredBuyerContact?.let { toEntity(it) }, - responsiblePersonSignature = response.responsiblePersonSignature, - sizeGradingCategories = response.sizeGradingCategories, - supplierInvoiceInfo = response.supplierInvoiceInfo, - takeOverDeclarationInfo = response.takeOverDeclarationInfo, - traceabilityRecordingSystemArt58 = response.traceabilityRecordingSystemArt58, - traceabilitySupplierIdentificationSystem = response.traceabilitySupplierIdentificationSystem, - tractorRegistrationNumber = response.tractorRegistrationNumber, - trailerRegistrationNumber = response.trailerRegistrationNumber, - transportDocumentInfo = response.transportDocumentInfo, - transporterComments = response.transporterComments, - transporterSignature = response.transporterSignature, - useOfUndersizedFisheryProducts = response.useOfUndersizedFisheryProducts, - vehicleNationality = response.vehicleNationality, - vehicleOwnerContact = response.vehicleOwnerContact?.let { toEntity(it) }, - vehicleType = response.vehicleType, - vesselOwnerContact = response.vesselOwnerContact?.let { toEntity(it) } + id = response?.id, + vessel = response?.vessel?.toEntity(), + actionId = response?.actionId.orEmpty(), + resource = response?.resource?.toEntity(), + endDatetimeUtc = response?.endDatetimeUtc, + startDatetimeUtc = response?.startDatetimeUtc, + module = response?.module?: SatiModuleType.M1, + inspectors = response?.inspectors?.map { it.toEntity() } ?: listOf() ) } - private fun fromEntity(entity: ContactEntity): Contact { + private fun ContactEntity.toOutput(): Contact { return Contact( - id = entity.id, - fullName = entity.fullName, - nationality = entity.nationality, - email = entity.email, - phone = entity.phone, - address = entity.address?.let { fromEntity(it) }, - createdAt = entity.createdAt - ) + id = id, + email = email, + phone = phone, + fullName = fullName, + lastName = lastName, + firstName = firstName, + nationality = nationality, + address = address?.toOutput(), + + ) } - private fun toEntity(response: Contact): ContactEntity { + private fun Contact.toEntity(): ContactEntity { return ContactEntity( - id = response.id, - fullName = response.fullName, - nationality = response.nationality, - email = response.email, - phone = response.phone, - address = response.address?.let { toEntity(it) }, - createdAt = response.createdAt + id = id, + email = email, + phone = phone, + fullName = fullName, + lastName = lastName, + firstName = firstName, + nationality = nationality, + address = address?.toEntity() ) } - private fun fromEntity(entity: AddressEntity): Address { + private fun AddressEntity.toOutput(): Address { return Address( - id = entity.id, - street = entity.street, - zipcode = entity.zipcode, - town = entity.town, - country = entity.country, - lat = entity.lat, - lng = entity.lng, - createdAt = entity.createdAt + id = id, + lng = lng, + lat = lat, + town = town, + zipcode = zipcode, + country = country, + fullAddress = fullAddress, + street = street?: fullAddress ) } - private fun toEntity(response: Address): AddressEntity { + private fun Address.toEntity(): AddressEntity { return AddressEntity( - id = response.id, - street = response.street, - zipcode = response.zipcode, - town = response.town, - country = response.country, - lat = response.lat, - lng = response.lng, - createdAt = response.createdAt + id = id, + lat = lat, + lng = lng, + town = town, + street = street, + zipcode = zipcode, + country = country, + fullAddress = fullAddress + ) + } + + private fun SatiParty.toEntity(): SatiPartyEntity { + return SatiPartyEntity( + id = id, + partyType = partyType, + comments = comments, + signature = signature, + contact = contact?.toEntity(), + ) + } + + private fun SatiPartyEntity.toOutput(): SatiParty { + return SatiParty( + id = id, + partyType = partyType, + comments = comments, + signature = signature, + contact = contact?.toOutput() + ) + } + + private fun SatiVessel.toEntity(): SatiVesselEntity { + return SatiVesselEntity( + id = id, + pnoType = pnoType, + tripNumber = tripNumber, + isMasterOwner = isMasterOwner, + jpe = jpe?.toEntity(), + ircs = ircs, + imo = imo, + type = type, + name = name, + length = length, + immat = immat, + extRef = extRef, + owner = owner?.toEntity(), + charterer = charterer?.toEntity(), + agent = agent?.toEntity(), + master = master?.toEntity(), + flagState = flagState + ) + } + + private fun SatiVesselEntity.toOutput(): SatiVessel { + return SatiVessel( + id = id, + pnoType = pnoType, + tripNumber = tripNumber, + isMasterOwner = isMasterOwner, + jpe = jpe?.toOutput(), + ircs = ircs, + imo = imo, + type = type, + name = name, + length = length, + immat = immat, + extRef = extRef, + owner = owner?.toOutput(), + charterer = charterer?.toOutput(), + agent = agent?.toOutput(), + master = master?.toOutput(), + flagState = flagState + ) + } + + private fun SatiInspector.toEntity(): SatiInspectorEntity { + return SatiInspectorEntity( + id = id, + agentId = agentId, + party = party?.toEntity(), + isOutOfUnit = isOutOfUnit, + authorityType = authorityType, + ) + } + + private fun SatiInspectorEntity.toOutput(): SatiInspector { + return SatiInspector( + id = id, + agentId = agentId, + isOutOfUnit = isOutOfUnit, + party = party?.toOutput(), + authorityType = authorityType + ) + } + + private fun SatiJpe.toEntity(): SatiJpeEntity { + return SatiJpeEntity( + pnoId = pnoId, + portId = portId, + pnoType = pnoType, + portName = portName, + tripNumber = tripNumber, + lastStopDate = lastStopDate + ) + } + + private fun SatiJpeEntity.toOutput(): SatiJpe { + return SatiJpe( + pnoId = pnoId, + portId = portId, + pnoType = pnoType, + portName = portName, + tripNumber = tripNumber, + lastStopDate = lastStopDate + ) + } + + + private fun ControlResource.toEntity(): ControlResourceEntity { + return ControlResourceEntity( + id = id, + name = name, + radioFrequency = radioFrequency, + registrationId = registrationId + ) + } + + private fun ControlResourceEntity.toOutput(): ControlResource { + return ControlResource( + id = id, + name = name, + radioFrequency = radioFrequency, + registrationId = registrationId ) } } diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiParty.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiParty.kt new file mode 100644 index 000000000..38ed7a306 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiParty.kt @@ -0,0 +1,11 @@ +package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati + +import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Contact + +data class SatiParty( + val id: Int? = null, + val partyType: String? = null, + val comments: String? = null, + val signature: Boolean = false, + val contact: Contact? = null, +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiVessel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiVessel.kt new file mode 100644 index 000000000..d90952738 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiVessel.kt @@ -0,0 +1,23 @@ +package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati + +import com.neovisionaries.i18n.CountryCode + +data class SatiVessel( + val id: Int? = null, + val jpe: SatiJpe? = null, + val type: String? = null, + val name: String? = null, + val immat: String? = null, + val imo: String? = null, + val length: Double? = null, + val extRef: String? = null, + val ircs: String? = null, + val owner: SatiParty? = null, + val flagState: CountryCode? = null, + val charterer: SatiParty? = null, + val pnoType: String? = null, + val tripNumber: String? = null, + val agent: SatiParty? = null, + val master: SatiParty? = null, + val isMasterOwner: Boolean = false +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/v2/MissionFishAction.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/v2/MissionFishAction.kt index d9b93d863..f1b3e53eb 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/v2/MissionFishAction.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/v2/MissionFishAction.kt @@ -7,6 +7,7 @@ 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.v2.MissionActionEntity import fr.gouv.dgampa.rapportnav.domain.entities.mission.v2.MissionFishActionEntity +import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.SatiMapper class MissionFishAction( @@ -97,7 +98,8 @@ class MissionFishAction( targets = fishAction.targets?.map { Target.fromTargetEntity(it) }?.sortedBy { it.startDateTimeUtc }, fishInfractions = fishAction.fishInfractions, incidentDuringOperation = fishAction.incidentDuringOperation, - hasDivingDuringOperation = fishAction.hasDivingDuringOperation + hasDivingDuringOperation = fishAction.hasDivingDuringOperation, + sati = SatiMapper.fromEntity(fishAction.sati) ) ) } diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt index 6bcb29fa8..c86edab59 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt @@ -1,53 +1,53 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati -import com.neovisionaries.i18n.CountryCode -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.AddressEntity -import jakarta.persistence.* +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant -import java.util.* @Entity @Table(name = "address") @EntityListeners(AuditingEntityListener::class) -data class AddressModel( + class AddressModel( @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false, updatable = false) - val id: UUID? = null, + var id: Int? = null, - @Column(name = "street", nullable = true, length = 255) - val street: String? = null, + @Column(name = "street", length = 255) + var street: String? = null, - @Column(name = "zipcode", nullable = true, length = 20) - val zipcode: String? = null, + @Column(name = "full_address", length = 400) + var fullAddress: String? = null, - @Column(name = "town", nullable = true, length = 100) - val town: String? = null, + @Column(name = "zipcode", length = 20) + var zipcode: String? = null, - @Column(name = "country", nullable = true, length = 3) - val country: String? = null, + @Column(name = "town", length = 100) + var town: String? = null, - @Column(name = "longitude", nullable = true) - val lng: Double? = null, + @Column(name = "country", length = 3) + var country: String? = null, - @Column(name = "latitude", nullable = true) - val lat: Double? = null, + @Column(name = "longitude") + var lng: Double? = null, + + @Column(name = "latitude") + var lat: Double? = null, @CreatedDate - @Column(name = "created_at", nullable = false) - val createdAt: Instant? = null -) { - fun toAddressEntity(): AddressEntity = AddressEntity( - id = this.id, - street = this.street, - zipcode = this.zipcode, - town = this.town, - country = CountryCode.getByCode(this.country), - lat = this.lat, - lng = this.lng, - createdAt = this.createdAt - ) -} + @Column(name = "created_at", nullable = false, updatable = false) + var createdAt: Instant? = null, + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt index 47b0744a0..24fb48167 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt @@ -5,42 +5,53 @@ import jakarta.persistence.Entity import jakarta.persistence.EntityListeners import jakarta.persistence.FetchType import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType import jakarta.persistence.Id import jakarta.persistence.JoinColumn import jakarta.persistence.OneToOne import jakarta.persistence.Table import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant -import java.util.UUID @Entity @Table(name = "contact") @EntityListeners(AuditingEntityListener::class) -data class ContactModel( +class ContactModel( @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false, updatable = false) - val id: UUID? = null, + var id: Int? = null, @Column(name = "full_name", length = 255) - val fullName: String? = null, + var fullName: String? = null, - @Column(name = "nationality", length = 3) - val nationality: String? = null, + @Column(name = "first_name", length = 255) + var firstName: String? = null, + + @Column(name = "last_name", length = 255) + var lastName: String? = null, + + @Column(name = "nationality", length = 16) + var nationality: String? = null, @Column(name = "email", length = 255) - val email: String? = null, + var email: String? = null, @Column(name = "phone", length = 50) - val phone: String? = null, + var phone: String? = null, @OneToOne(fetch = FetchType.LAZY, optional = true) @JoinColumn(name = "address_id", referencedColumnName = "id") - val address: AddressModel? = null, + var address: AddressModel? = null, @CreatedDate - @Column(name = "created_at", nullable = false) - val createdAt: Instant? = null + @Column(name = "created_at", nullable = false, updatable = false) + var createdAt: Instant? = null, + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt new file mode 100644 index 000000000..818c0d4d0 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt @@ -0,0 +1,48 @@ +package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.Instant + +@Entity +@Table(name = "sati_inspector") +@EntityListeners(AuditingEntityListener::class) +class SatiInspectorModel( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false, updatable = false) + var id: Int? = null, + + @OneToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "party_id", referencedColumnName = "id") + var party: SatiPartyModel? = null, + + @Column(name = "authority_type", length = 50) + var authorityType: String? = null, + + @Column(name = "agent_id", length = 255) + var agentId: Int? = null, + + @Column(name = "is_out_of_unit", nullable = false) + var isOutOfUnit: Boolean = false, + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + var createdAt: Instant? = null, + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt index d201efd40..4c950a4b9 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt @@ -1,211 +1,70 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati -import jakarta.persistence.* +import com.fasterxml.jackson.annotation.JsonIgnore +import jakarta.persistence.CascadeType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToMany +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import org.hibernate.annotations.UuidGenerator import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant -import java.util.* +import java.util.Objects +import java.util.UUID @Entity @Table(name = "sati") @EntityListeners(AuditingEntityListener::class) -data class SatiModel( +class SatiModel( @Id @GeneratedValue + @UuidGenerator @Column(nullable = false, updatable = false) - val id: UUID? = null, - - @Column(nullable = false, length = 2) - val module: String, - - @Column(name = "action_id", unique = true, nullable = false) - val actionId: String, + var id: UUID? = null, + + @Column(name = "module", nullable = false, length = 50) + var module: String, + + @Column(name = "action_id", unique = true, nullable = false, length = 255) + var actionId: String, + + @Column(name = "resource_id") + var resourceId: Int? = null, + + @OneToOne( + fetch = FetchType.LAZY, + cascade = [CascadeType.ALL], + orphanRemoval = true, + optional = true + ) + @JoinColumn(name = "vessel_id", referencedColumnName = "id") + var vessel: SatiVesselModel? = null, + + @JsonIgnore + @OneToMany( + fetch = FetchType.LAZY, + cascade = [CascadeType.ALL], + orphanRemoval = true + ) + @JoinColumn(name = "sati_id", referencedColumnName = "id") + var inspectors: MutableList = mutableListOf(), @CreatedDate - @Column(name = "created_at", nullable = false) + @Column(name = "created_at", nullable = false, updatable = false) val createdAt: Instant? = null, @LastModifiedDate @Column(name = "updated_at", nullable = false) - val updatedAt: Instant? = null, - - @Column(name = "action_taken", length = 100) - val actionTaken: String? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "agent_contact_id", referencedColumnName = "id") - val agentContact: ContactModel? = null, - - @Column(name = "appointing_authority", length = 50) - val appointingAuthority: String? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "beneficial_owner_contact_id", referencedColumnName = "id") - val beneficialOwnerContact: ContactModel? = null, - - @Column(name = "catch_certificate_info") - val catchCertificateInfo: Boolean? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "charterer_contact_id", referencedColumnName = "id") - val chartererContact: ContactModel? = null, - - @Column(name = "common_marketing_standards") - val commonMarketingStandards: Boolean? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "driver_contact_id", referencedColumnName = "id") - val driverContact: ContactModel? = null, - - @Column(name = "eu_fishing_trip_id", length = 50) - val euFishingTripId: String? = null, - - @Column(name = "fishery_lot_labelling") - val fisheryLotLabelling: Boolean? = null, - - @Column(name = "fishery_product_documents_inspection") - val fisheryProductDocumentsInspection: Boolean? = null, - - @Column(name = "fishery_product_storage_mechanism_inspected") - val fisheryProductStorageMechanismInspected: Boolean? = null, - - @Column(name = "fishery_product_weighed_before_sale") - val fisheryProductWeighedBeforeSale: Boolean? = null, - - @Column(name = "freshness_categories", columnDefinition = "text") - val freshnessCategories: String? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "importer_contact_id", referencedColumnName = "id") - val importerContact: ContactModel? = null, - - @Column(name = "infringements_observations", columnDefinition = "text") - val infringementsObservations: String? = null, - - @Column(name = "inspection_end_datetime_utc") - val inspectionEndDatetimeUtc: Instant? = null, - - @Column(name = "inspection_start_datetime_utc", nullable = false) - val inspectionStartDatetimeUtc: Instant, - - @Column(name = "inspection_location_address_id") - val inspectionLocationAddressId: String? = null, - - @Column(name = "inspector_comments", columnDefinition = "text") - val inspectorComments: String? = null, - - @Column(name = "inspector_name_or_id", length = 255) - val inspectorNameOrId: String? = null, - - @Column(name = "inspector_signature") - val inspectorSignature: Boolean? = null, - - @Column(name = "inspector_unique_id", length = 50) - val inspectorUniqueId: String? = null, - - @Column(name = "landing_declaration_info") - val landingDeclarationInfo: Boolean? = null, - - @Column(name = "lot_id_species_quantities", columnDefinition = "jsonb") - val lotIdSpeciesQuantities: String? = null, - - @Column(name = "lots_art58_compliance") - val lotsArt58Compliance: Boolean? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "market_owner_contact_id", referencedColumnName = "id") - val marketOwnerContact: ContactModel? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "market_owner_representative_contact_id", referencedColumnName = "id") - val marketOwnerRepresentativeContact: ContactModel? = null, - - @Column(name = "market_premises_address_id") - val marketPremisesAddressId: String? = null, - - @Column(name = "market_premises_location", length = 255) - val marketPremisesLocation: String? = null, - - @Column(name = "market_premises_name", length = 255) - val marketPremisesName: String? = null, - - @Column(name = "master_comments", columnDefinition = "text") - val masterComments: String? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "master_contact_id", referencedColumnName = "id") - val masterContact: ContactModel? = null, - - @Column(name = "new_lot_composition_info", columnDefinition = "jsonb") - val newLotCompositionInfo: String? = null, - - @Column(name = "operator_comments", columnDefinition = "text") - val operatorComments: String? = null, - - @Column(name = "operator_signature") - val operatorSignature: Boolean? = null, - - @Column(name = "patrol_vessel_external_marking", length = 50) - val patrolVesselExternalMarking: String? = null, - - @Column(name = "patrol_vessel_radio_call_sign", length = 20) - val patrolVesselRadioCallSign: String? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "registered_buyer_contact_id", referencedColumnName = "id") - val registeredBuyerContact: ContactModel? = null, - - @Column(name = "responsible_person_signature") - val responsiblePersonSignature: Boolean? = null, - - @Column(name = "size_grading_categories", columnDefinition = "text") - val sizeGradingCategories: String? = null, - - @Column(name = "supplier_invoice_info") - val supplierInvoiceInfo: Boolean? = null, - - @Column(name = "take_over_declaration_info") - val takeOverDeclarationInfo: Boolean? = null, - - @Column(name = "traceability_recording_system_art58") - val traceabilityRecordingSystemArt58: Boolean? = null, - - @Column(name = "traceability_supplier_identification_system") - val traceabilitySupplierIdentificationSystem: Boolean? = null, - - @Column(name = "tractor_registration_number", length = 20) - val tractorRegistrationNumber: String? = null, - - @Column(name = "trailer_registration_number", length = 20) - val trailerRegistrationNumber: String? = null, - - @Column(name = "transport_document_info") - val transportDocumentInfo: Boolean? = null, - - @Column(name = "transporter_comments", columnDefinition = "text") - val transporterComments: String? = null, - - @Column(name = "transporter_signature") - val transporterSignature: Boolean? = null, - - @Column(name = "use_of_undersized_fishery_products") - val useOfUndersizedFisheryProducts: Boolean? = null, - - @Column(name = "vehicle_nationality", length = 3) - val vehicleNationality: String? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "vehicle_owner_contact_id", referencedColumnName = "id") - val vehicleOwnerContact: ContactModel? = null, - - @Column(name = "vehicle_type", length = 50) - val vehicleType: String? = null, - - @OneToOne(fetch = FetchType.LAZY, optional = true) - @JoinColumn(name = "vessel_owner_contact_id", referencedColumnName = "id") - val vesselOwnerContact: ContactModel? = null + val updatedAt: Instant? = null ) { override fun hashCode(): Int { return Objects.hash(id) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt index 89c05d86d..dd94a5031 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt @@ -1,192 +1,169 @@ -package fr.gouv.dgampa.rapportnav.infrastructure.database.repositories.mission.sati +package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati import com.neovisionaries.i18n.CountryCode -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.AddressEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.ContactEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity -import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.AddressModel -import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.ContactModel -import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.SatiModel +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* object SatiModelMapper { - fun toEntity(model: SatiModel): SatiEntity { return SatiEntity( id = model.id, - module = model.module, actionId = model.actionId, + resource = ControlResourceEntity( + id = model.resourceId + ), + vessel = model.vessel?.toEntity(), createdAt = model.createdAt, updatedAt = model.updatedAt, - actionTaken = model.actionTaken, - agentContact = model.agentContact?.toEntity(), - appointingAuthority = model.appointingAuthority, - beneficialOwnerContact = model.beneficialOwnerContact?.toEntity(), - catchCertificateInfo = model.catchCertificateInfo, - chartererContact = model.chartererContact?.toEntity(), - commonMarketingStandards = model.commonMarketingStandards, - driverContact = model.driverContact?.toEntity(), - euFishingTripId = model.euFishingTripId, - fisheryLotLabelling = model.fisheryLotLabelling, - fisheryProductDocumentsInspection = model.fisheryProductDocumentsInspection, - fisheryProductStorageMechanismInspected = model.fisheryProductStorageMechanismInspected, - fisheryProductWeighedBeforeSale = model.fisheryProductWeighedBeforeSale, - freshnessCategories = model.freshnessCategories, - importerContact = model.importerContact?.toEntity(), - infringementsObservations = model.infringementsObservations, - inspectionEndDatetimeUtc = model.inspectionEndDatetimeUtc, - inspectionStartDatetimeUtc = model.inspectionStartDatetimeUtc, - inspectionLocationAddressId = model.inspectionLocationAddressId, - inspectorComments = model.inspectorComments, - inspectorNameOrId = model.inspectorNameOrId, - inspectorSignature = model.inspectorSignature, - inspectorUniqueId = model.inspectorUniqueId, - landingDeclarationInfo = model.landingDeclarationInfo, - lotIdSpeciesQuantities = model.lotIdSpeciesQuantities, - lotsArt58Compliance = model.lotsArt58Compliance, - marketOwnerContact = model.marketOwnerContact?.toEntity(), - marketOwnerRepresentativeContact = model.marketOwnerRepresentativeContact?.toEntity(), - marketPremisesAddressId = model.marketPremisesAddressId, - marketPremisesLocation = model.marketPremisesLocation, - marketPremisesName = model.marketPremisesName, - masterComments = model.masterComments, - masterContact = model.masterContact?.toEntity(), - newLotCompositionInfo = model.newLotCompositionInfo, - operatorComments = model.operatorComments, - operatorSignature = model.operatorSignature, - patrolVesselExternalMarking = model.patrolVesselExternalMarking, - patrolVesselRadioCallSign = model.patrolVesselRadioCallSign, - registeredBuyerContact = model.registeredBuyerContact?.toEntity(), - responsiblePersonSignature = model.responsiblePersonSignature, - sizeGradingCategories = model.sizeGradingCategories, - supplierInvoiceInfo = model.supplierInvoiceInfo, - takeOverDeclarationInfo = model.takeOverDeclarationInfo, - traceabilityRecordingSystemArt58 = model.traceabilityRecordingSystemArt58, - traceabilitySupplierIdentificationSystem = model.traceabilitySupplierIdentificationSystem, - tractorRegistrationNumber = model.tractorRegistrationNumber, - trailerRegistrationNumber = model.trailerRegistrationNumber, - transportDocumentInfo = model.transportDocumentInfo, - transporterComments = model.transporterComments, - transporterSignature = model.transporterSignature, - useOfUndersizedFisheryProducts = model.useOfUndersizedFisheryProducts, - vehicleNationality = CountryCode.getByAlpha3Code(model.vehicleNationality), - vehicleOwnerContact = model.vehicleOwnerContact?.toEntity(), - vehicleType = model.vehicleType, - vesselOwnerContact = model.vesselOwnerContact?.toEntity() + inspectors = model.inspectors.map { it.toEntity() }, + module = model.module.let { SatiModuleType.valueOf(it) }, ) } fun toModel(entity: SatiEntity): SatiModel { return SatiModel( id = entity.id, - module = entity.module, actionId = entity.actionId, createdAt = entity.createdAt, updatedAt = entity.updatedAt, - actionTaken = entity.actionTaken, - agentContact = entity.agentContact?.toModel(), - appointingAuthority = entity.appointingAuthority, - beneficialOwnerContact = entity.beneficialOwnerContact?.toModel(), - catchCertificateInfo = entity.catchCertificateInfo, - chartererContact = entity.chartererContact?.toModel(), - commonMarketingStandards = entity.commonMarketingStandards, - driverContact = entity.driverContact?.toModel(), - euFishingTripId = entity.euFishingTripId, - fisheryLotLabelling = entity.fisheryLotLabelling, - fisheryProductDocumentsInspection = entity.fisheryProductDocumentsInspection, - fisheryProductStorageMechanismInspected = entity.fisheryProductStorageMechanismInspected, - fisheryProductWeighedBeforeSale = entity.fisheryProductWeighedBeforeSale, - freshnessCategories = entity.freshnessCategories, - importerContact = entity.importerContact?.toModel(), - infringementsObservations = entity.infringementsObservations, - inspectionEndDatetimeUtc = entity.inspectionEndDatetimeUtc, - inspectionStartDatetimeUtc = entity.inspectionStartDatetimeUtc, - inspectionLocationAddressId = entity.inspectionLocationAddressId, - inspectorComments = entity.inspectorComments, - inspectorNameOrId = entity.inspectorNameOrId, - inspectorSignature = entity.inspectorSignature, - inspectorUniqueId = entity.inspectorUniqueId, - landingDeclarationInfo = entity.landingDeclarationInfo, - lotIdSpeciesQuantities = entity.lotIdSpeciesQuantities, - lotsArt58Compliance = entity.lotsArt58Compliance, - marketOwnerContact = entity.marketOwnerContact?.toModel(), - marketOwnerRepresentativeContact = entity.marketOwnerRepresentativeContact?.toModel(), - marketPremisesAddressId = entity.marketPremisesAddressId, - marketPremisesLocation = entity.marketPremisesLocation, - marketPremisesName = entity.marketPremisesName, - masterComments = entity.masterComments, - masterContact = entity.masterContact?.toModel(), - newLotCompositionInfo = entity.newLotCompositionInfo, - operatorComments = entity.operatorComments, - operatorSignature = entity.operatorSignature, - patrolVesselExternalMarking = entity.patrolVesselExternalMarking, - patrolVesselRadioCallSign = entity.patrolVesselRadioCallSign, - registeredBuyerContact = entity.registeredBuyerContact?.toModel(), - responsiblePersonSignature = entity.responsiblePersonSignature, - sizeGradingCategories = entity.sizeGradingCategories, - supplierInvoiceInfo = entity.supplierInvoiceInfo, - takeOverDeclarationInfo = entity.takeOverDeclarationInfo, - traceabilityRecordingSystemArt58 = entity.traceabilityRecordingSystemArt58, - traceabilitySupplierIdentificationSystem = entity.traceabilitySupplierIdentificationSystem, - tractorRegistrationNumber = entity.tractorRegistrationNumber, - trailerRegistrationNumber = entity.trailerRegistrationNumber, - transportDocumentInfo = entity.transportDocumentInfo, - transporterComments = entity.transporterComments, - transporterSignature = entity.transporterSignature, - useOfUndersizedFisheryProducts = entity.useOfUndersizedFisheryProducts, - vehicleNationality = entity.vehicleNationality?.alpha3, - vehicleOwnerContact = entity.vehicleOwnerContact?.toModel(), - vehicleType = entity.vehicleType, - vesselOwnerContact = entity.vesselOwnerContact?.toModel() + resourceId = entity.resource?.id, + vessel = entity.vessel?.toModel(), + module = entity.module.toString(), + inspectors = entity.inspectors?.map { it.toModel() }?.toMutableList() ?: mutableListOf() + ) + } + + private fun AddressModel.toEntity(): AddressEntity { + return AddressEntity( + id = id, + lng = lng, + lat = lat, + town = town, + street = street, + zipcode = zipcode, + createdAt = createdAt, + updatedAt = updatedAt, + fullAddress = fullAddress, + country = country?.let { CountryCode.getByAlpha3Code(it) } + ) + } + + private fun AddressEntity.toModel(): AddressModel { + return AddressModel( + id = id, + lng = lng, + lat = lat, + town = town, + street = street, + zipcode = zipcode, + createdAt = createdAt, + updatedAt = updatedAt, + fullAddress = fullAddress, + country = country?.alpha3 ) } private fun ContactModel.toEntity(): ContactEntity { return ContactEntity( id = id, - fullName = fullName, - nationality = CountryCode.getByAlpha3Code(nationality), email = email, phone = phone, - address = address?.toEntity(), - createdAt = createdAt + fullName = fullName, + lastName = lastName, + firstName = firstName, + createdAt = createdAt, + updatedAt = updatedAt, + nationality = nationality, + address = address?.toEntity() ) } private fun ContactEntity.toModel(): ContactModel { return ContactModel( id = id, - fullName = fullName, - nationality = nationality?.alpha3, email = email, phone = phone, - address = address?.toModel(), - createdAt = createdAt + fullName = fullName, + lastName = lastName, + firstName = firstName, + createdAt = createdAt, + updatedAt = updatedAt, + nationality = nationality, + address = address?.toModel() ) } - private fun AddressModel.toEntity(): AddressEntity { - return AddressEntity( + private fun SatiPartyModel.toEntity(): SatiPartyEntity { + return SatiPartyEntity( id = id, - street = street, - zipcode = zipcode, - town = town, - lat = lat, - lng = lng, - country = CountryCode.getByAlpha3Code(country), - createdAt = createdAt + partyType = partyType, + comments = comments, + signature = signature, + contact = contact?.toEntity(), + createdAt = createdAt, + updatedAt = updatedAt ) } - private fun AddressEntity.toModel(): AddressModel { - return AddressModel( + private fun SatiPartyEntity.toModel(): SatiPartyModel { + return SatiPartyModel( id = id, - street = street, - zipcode = zipcode, - town = town, - lat = lat, - lng = lng, - country = country?.alpha3, - createdAt = createdAt + partyType = partyType, + comments = comments, + signature = signature, + createdAt = createdAt, + updatedAt = updatedAt, + contact = contact?.toModel() + ) + } + + private fun SatiVesselModel.toEntity(): SatiVesselEntity { + return SatiVesselEntity( + id = id, + pnoType = pnoType, + createdAt = createdAt, + updatedAt = updatedAt, + tripNumber = tripNumber, + agent = agent?.toEntity(), + master = master?.toEntity(), + isMasterOwner = isMasterOwner + ) + } + + private fun SatiVesselEntity.toModel(): SatiVesselModel { + return SatiVesselModel( + id = id, + pnoType = pnoType, + createdAt = createdAt, + updatedAt = updatedAt, + tripNumber = tripNumber, + agent = agent?.toModel(), + master = master?.toModel(), + isMasterOwner = isMasterOwner + ) + } + + private fun SatiInspectorModel.toEntity(): SatiInspectorEntity { + return SatiInspectorEntity( + id = id, + agentId = agentId, + party = party?.toEntity(), + isOutOfUnit = isOutOfUnit, + createdAt = createdAt, + updatedAt = updatedAt, + authorityType = authorityType?.let { AuthorityType.valueOf(it) }, + ) + } + + private fun SatiInspectorEntity.toModel(): SatiInspectorModel { + return SatiInspectorModel( + id = id, + agentId = agentId, + party = party?.toModel(), + authorityType = authorityType?.toString(), + isOutOfUnit = isOutOfUnit, + createdAt = createdAt, + updatedAt = updatedAt ) } } diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt new file mode 100644 index 000000000..d6863f77f --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt @@ -0,0 +1,48 @@ +package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.FetchType +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.JoinColumn +import jakarta.persistence.OneToOne +import jakarta.persistence.Table +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.Instant + +@Entity +@Table(name = "sati_party") +@EntityListeners(AuditingEntityListener::class) +class SatiPartyModel( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false, updatable = false) + var id: Int? = null, + + @Column(name = "party_type", length = 50) + var partyType: String? = null, + + @Column(name = "comments") + var comments: String? = null, + + @Column(name = "signature", nullable = false) + var signature: Boolean = false, + + @OneToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "contact_id", referencedColumnName = "id") + var contact: ContactModel? = null, + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + var createdAt: Instant? = null, + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt new file mode 100644 index 000000000..89a6fa884 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt @@ -0,0 +1,43 @@ +package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati + +import jakarta.persistence.* +import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedDate +import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.Instant + +@Entity +@Table(name = "sati_vessel") +@EntityListeners(AuditingEntityListener::class) +class SatiVesselModel( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false, updatable = false) + var id: Int? = null, + + @Column(name = "pno_type", length = 50) + var pnoType: String? = null, + + @Column(name = "trip_number", length = 50) + var tripNumber: String? = null, + + @OneToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "agent_id", referencedColumnName = "id") + var agent: SatiPartyModel? = null, + + @OneToOne(fetch = FetchType.LAZY, optional = true) + @JoinColumn(name = "master_id", referencedColumnName = "id") + var master: SatiPartyModel? = null, + + @Column(name = "is_master_owner", nullable = false) + var isMasterOwner: Boolean = false, + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + var createdAt: Instant? = null, + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + var updatedAt: Instant? = null +) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepository.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepository.kt index 4d2b1473a..923d944f6 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepository.kt @@ -5,6 +5,7 @@ import fr.gouv.dgampa.rapportnav.domain.exceptions.BackendInternalException import fr.gouv.dgampa.rapportnav.domain.exceptions.BackendUsageErrorCode import fr.gouv.dgampa.rapportnav.domain.exceptions.BackendUsageException import fr.gouv.dgampa.rapportnav.domain.repositories.mission.sati.ISatiRepository +import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.SatiModelMapper import fr.gouv.dgampa.rapportnav.infrastructure.database.repositories.interfaces.mission.sati.IDBSatiRepository import org.springframework.dao.InvalidDataAccessApiUsageException import org.springframework.stereotype.Repository diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/monitorfish/output/MissionActionDataOutput.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/monitorfish/output/MissionActionDataOutput.kt index 6e390273c..fa41fc91d 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/monitorfish/output/MissionActionDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/monitorfish/output/MissionActionDataOutput.kt @@ -106,7 +106,21 @@ data class MissionActionDataOutput( val observationsByUnit: String? = null, val isDeleted: Boolean, val hasDivingDuringOperation: Boolean? = null, - val incidentDuringOperation: Boolean? = null + val incidentDuringOperation: Boolean? = null, + val vesselLength: Double? = null, + val vesselType: String? = null, + val imo: String? = null, + val proprietorName: String? = null, + val proprietorPhones: List? = null, + val proprietorEmails: List? = null, + val proprietorNationality: String? = null, + val proprietorAddress: String? = null, + val tripNumber: String? = null, + val pnoReportId: String? = null, + val pnoPurpose: LogbookMessagePurpose? = null, + val lastDeparturePortLocode: String? = null, + val lastDeparturePortName: String? = null, + val lastDepartureDateTime: ZonedDateTime? = null ) { fun toMissionAction(): MissionAction { return MissionAction( @@ -164,7 +178,21 @@ data class MissionActionDataOutput( observationsByUnit = this.observationsByUnit, speciesQuantitySeized = this.speciesQuantitySeized, incidentDuringOperation = this.incidentDuringOperation, - hasDivingDuringOperation = this.hasDivingDuringOperation + hasDivingDuringOperation = this.hasDivingDuringOperation, + vesselLength = this.vesselLength, + vesselType = this.vesselType, + imo = this.imo, + proprietorName = this.proprietorName, + proprietorPhones = this.proprietorPhones, + proprietorEmails = this.proprietorEmails, + proprietorNationality = this.proprietorNationality, + proprietorAddress = this.proprietorAddress, + tripNumber = this.tripNumber, + pnoReportId = this.pnoReportId, + pnoPurpose = this.pnoPurpose, + lastDeparturePortLocode = this.lastDeparturePortLocode, + lastDeparturePortName = this.lastDeparturePortName, + lastDepartureDateTime = this.lastDepartureDateTime?.toInstant(), ) } } diff --git a/backend/src/main/resources/db/migration/V1.2026.06.22.15.55__update_sati_table_and_correspondance.sql b/backend/src/main/resources/db/migration/V1.2026.06.22.15.55__update_sati_table_and_correspondance.sql new file mode 100644 index 000000000..2f2b80152 --- /dev/null +++ b/backend/src/main/resources/db/migration/V1.2026.06.22.15.55__update_sati_table_and_correspondance.sql @@ -0,0 +1,92 @@ + +DROP TABLE IF EXISTS sati_inspector; +DROP TABLE IF EXISTS sati; +DROP TABLE IF EXISTS sati_vessel; +DROP TABLE IF EXISTS sati_party; +DROP TABLE IF EXISTS contact; +DROP TABLE IF EXISTS address; + +CREATE TABLE address +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + street VARCHAR(255), + full_address VARCHAR(400), + zipcode VARCHAR(20), + town VARCHAR(100), + country VARCHAR(3), + longitude DOUBLE PRECISION, + latitude DOUBLE PRECISION, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE contact +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + full_name VARCHAR(255), + first_name VARCHAR(255), + last_name VARCHAR(255), + nationality VARCHAR(16), + email VARCHAR(255), + phone VARCHAR(50), + address_id INTEGER REFERENCES address (id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE sati_party +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + party_type VARCHAR(50), + comments TEXT, + signature BOOLEAN NOT NULL DEFAULT false, + contact_id INTEGER REFERENCES contact (id), + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE sati_vessel +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + pno_type VARCHAR(50), + trip_number VARCHAR(50), + agent_id INTEGER REFERENCES sati_party (id), + master_id INTEGER REFERENCES sati_party (id), + is_master_owner BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + + +CREATE TABLE sati +( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + module VARCHAR(50) NOT NULL, + action_id VARCHAR(255) NOT NULL UNIQUE, + resource_id INTEGER, + vessel_id INTEGER REFERENCES sati_vessel (id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + + +CREATE TABLE sati_inspector +( + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + sati_id UUID NOT NULL REFERENCES sati (id) ON DELETE CASCADE, + party_id INTEGER REFERENCES sati_party (id), + authority_type VARCHAR(50), + agent_id INTEGER, + is_out_of_unit BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + + +CREATE INDEX IF NOT EXISTS idx_contact_address_id ON contact (address_id); +CREATE INDEX IF NOT EXISTS idx_sati_party_contact_id ON sati_party (contact_id); +CREATE INDEX IF NOT EXISTS idx_sati_vessel_agent_id ON sati_vessel (agent_id); +CREATE INDEX IF NOT EXISTS idx_sati_vessel_master_id ON sati_vessel (master_id); +CREATE INDEX IF NOT EXISTS idx_sati_vessel_id ON sati (vessel_id); +CREATE INDEX IF NOT EXISTS idx_sati_inspector_sati_id ON sati_inspector (sati_id); +CREATE INDEX IF NOT EXISTS idx_sati_inspector_party_id ON sati_inspector (party_id); diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt new file mode 100644 index 000000000..b30134c97 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt @@ -0,0 +1,302 @@ +package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati + +import com.neovisionaries.i18n.CountryCode +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.* +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import java.time.Instant +import java.util.* + +class SatiEntityMapperTest { + + private val timestamp = Instant.parse("2026-03-24T09:15:30Z") + + private fun buildSatiEntity( + vessel: SatiVesselEntity? = SatiVesselEntity( + id = 10, + agent = SatiPartyEntity(id = 5, partyType = "AGENT"), + master = SatiPartyEntity(id = 6, partyType = "MASTER"), + tripNumber = "EXISTING-TRIP" + ), + inspectors: List = listOf( + SatiInspectorEntity(id = 7, agentId = 42, authorityType = AuthorityType.AECP) + ), + resource: ControlResourceEntity? = ControlResourceEntity(id = 99, name = "Vedette A") + ): SatiEntity { + return SatiEntity( + id = UUID.randomUUID(), + module = SatiModuleType.M1, + actionId = "old-action-id", + vessel = vessel, + resource = resource, + inspectors = inspectors, + createdAt = timestamp, + updatedAt = timestamp + ) + } + + private fun buildMissionAction( + id: Int = 123, + actionType: MissionActionType = MissionActionType.SEA_CONTROL + ): MissionAction { + return MissionAction( + id = id, + missionId = 1, + actionType = actionType, + actionDatetimeUtc = timestamp, + actionEndDatetimeUtc = timestamp.plusSeconds(3600), + userTrigram = "ABC", + isFromPoseidon = false, + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + completion = Completion.TO_COMPLETE, + vesselName = "Le Marin", + internalReferenceNumber = "CFR-123", + externalReferenceNumber = "EXT-001", + ircs = "FXYZ", + imo = "IMO1234567", + vesselLength = 25.5, + vesselType = "Trawler", + flagState = CountryCode.FR, + proprietorName = "Jean Dupont", + proprietorEmails = listOf("jean@example.com", "jean2@example.com"), + proprietorPhones = listOf("+33612345678"), + proprietorNationality = "FRA", + proprietorAddress = "1 rue de la mer, 75000 Paris", + tripNumber = "TRIP-001", + pnoReportId = "PNO-123", + pnoPurpose = LogbookMessagePurpose.LAN, + lastDeparturePortLocode = "FRLEH", + lastDeparturePortName = "Le Havre", + lastDepartureDateTime = timestamp + ) + } + + @Nested + inner class MergeWithMissionAction { + + @Test + fun `should preserve sati id and module`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.id).isEqualTo(sati.id) + assertThat(result.module).isEqualTo(sati.module) + } + + @Test + fun `should use action id as actionId`() { + val sati = buildSatiEntity() + val action = buildMissionAction(id = 456) + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.actionId).isEqualTo("456") + } + + @Test + fun `should preserve sati timestamps`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.createdAt).isEqualTo(sati.createdAt) + assertThat(result.updatedAt).isEqualTo(sati.updatedAt) + } + + @Test + fun `should map action datetimes to sati datetimes`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.startDatetimeUtc).isEqualTo(action.actionDatetimeUtc) + assertThat(result.endDatetimeUtc).isEqualTo(action.actionEndDatetimeUtc) + } + + @Test + fun `should map vessel identification from action`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.vessel?.name).isEqualTo("Le Marin") + assertThat(result.vessel?.immat).isEqualTo("CFR-123") + assertThat(result.vessel?.extRef).isEqualTo("EXT-001") + assertThat(result.vessel?.ircs).isEqualTo("FXYZ") + assertThat(result.vessel?.imo).isEqualTo("IMO1234567") + assertThat(result.vessel?.length).isEqualTo(25.5) + assertThat(result.vessel?.type).isEqualTo("Trawler") + assertThat(result.vessel?.flagState).isEqualTo(CountryCode.FR) + } + + @Test + fun `should map proprietor to owner contact`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + val owner = result.vessel?.owner + assertThat(owner?.contact?.fullName).isEqualTo("Jean Dupont") + assertThat(owner?.contact?.email).isEqualTo("jean@example.com") + assertThat(owner?.contact?.phone).isEqualTo("+33612345678") + assertThat(owner?.contact?.nationality).isEqualTo("FRA") + assertThat(owner?.contact?.address?.fullAddress).isEqualTo("1 rue de la mer, 75000 Paris") + } + + @Test + fun `should map jpe fields from action`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + val jpe = result.vessel?.jpe + assertThat(jpe?.pnoId).isEqualTo("PNO-123") + assertThat(jpe?.portId).isEqualTo("FRLEH") + assertThat(jpe?.portName).isEqualTo("Le Havre") + assertThat(jpe?.lastStopDate).isEqualTo(timestamp) + assertThat(jpe?.tripNumber).isEqualTo("TRIP-001") + assertThat(jpe?.pnoType).isEqualTo(LogbookMessagePurpose.LAN) + } + + @Test + fun `should fallback to sati tripNumber when action tripNumber is null`() { + val sati = buildSatiEntity() + val action = buildMissionAction().copy(tripNumber = null) + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.vessel?.jpe?.tripNumber).isEqualTo("EXISTING-TRIP") + } + + @Test + fun `should preserve sati resource`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.resource?.id).isEqualTo(99) + assertThat(result.resource?.name).isEqualTo("Vedette A") + } + + @Test + fun `should preserve sati inspectors`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.inspectors).hasSize(1) + assertThat(result.inspectors?.first()?.agentId).isEqualTo(42) + } + + @Test + fun `should preserve sati agent and master from vessel`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.vessel?.agent?.partyType).isEqualTo("AGENT") + assertThat(result.vessel?.master?.partyType).isEqualTo("MASTER") + } + + @Test + fun `should set charterer to null`() { + val sati = buildSatiEntity() + val action = buildMissionAction() + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.vessel?.charterer).isNull() + } + + @Test + fun `should handle null action id`() { + val sati = buildSatiEntity() + val action = buildMissionAction().copy(id = null) + val result = SatiEntityMapper.merge(sati, action) + + assertThat(result.actionId).isEmpty() + } + + @Test + fun `should handle null proprietor fields`() { + val sati = buildSatiEntity() + val action = buildMissionAction().copy( + proprietorName = null, + proprietorEmails = null, + proprietorPhones = null, + proprietorNationality = null, + proprietorAddress = null + ) + val result = SatiEntityMapper.merge(sati, action) + + val owner = result.vessel?.owner + assertThat(owner?.contact?.fullName).isNull() + assertThat(owner?.contact?.email).isNull() + assertThat(owner?.contact?.phone).isNull() + assertThat(owner?.contact?.nationality).isNull() + assertThat(owner?.contact?.address?.fullAddress).isNull() + } + } + + @Nested + inner class IsEquals { + + @Test + fun `should return true for equal entities`() { + val entity = buildSatiEntity() + val copy = entity.copy() + assertThat(SatiEntityMapper.isEquals(entity, copy)).isTrue() + } + + @Test + fun `should return false when fromDb is null`() { + val entity = buildSatiEntity() + assertThat(SatiEntityMapper.isEquals(null, entity)).isFalse() + } + + @Test + fun `should return false when entity is null`() { + val entity = buildSatiEntity() + assertThat(SatiEntityMapper.isEquals(entity, null)).isFalse() + } + + @Test + fun `should return false when both are null`() { + assertThat(SatiEntityMapper.isEquals(null, null)).isFalse() + } + + @Test + fun `should return false for entities with different ids`() { + val entity1 = buildSatiEntity() + val entity2 = entity1.copy(id = UUID.randomUUID()) + assertThat(SatiEntityMapper.isEquals(entity1, entity2)).isFalse() + } + } + + @Nested + inner class MergeWithSatiInput { + + @Test + fun `should return existing sati unchanged`() { + val sati = buildSatiEntity() + val input = fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Sati( + actionId = "new-action", + module = SatiModuleType.M3 + ) + val result = SatiEntityMapper.merge(sati, input) + assertThat(result).isEqualTo(sati) + } + + @Test + fun `should return null when sati is null`() { + val input = fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Sati( + actionId = "new-action", + module = SatiModuleType.M3 + ) + val result = SatiEntityMapper.merge(null, input) + assertThat(result).isNull() + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt index d47717771..a0dd3d0f9 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt @@ -4,7 +4,9 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.Comple import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionAction import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionActionType import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiModuleType import fr.gouv.dgampa.rapportnav.domain.repositories.mission.sati.ISatiRepository +import fr.gouv.dgampa.rapportnav.domain.repositories.v2.controlUnitResource.IEnvControlUnitResourceRepository import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test @@ -23,11 +25,13 @@ class GetComputeSatiTest { @MockitoBean private lateinit var satiRepo: ISatiRepository + @MockitoBean + private lateinit var controlResourceRepo: IEnvControlUnitResourceRepository + @Test fun `execute should throw IllegalArgumentException when action id is null`() { - val useCase = GetComputeSati(satiRepo) + val useCase = GetComputeSati(satiRepo, controlResourceRepo) val action = createAction(id = null, actionType = MissionActionType.AIR_CONTROL) - assertThrows(IllegalArgumentException::class.java) { useCase.execute(action) } @@ -37,7 +41,7 @@ class GetComputeSatiTest { @Test fun `execute should return null when action type is not a control`() { - val useCase = GetComputeSati(satiRepo) + val useCase = GetComputeSati(satiRepo, controlResourceRepo) val action = createAction(id = 761, actionType = MissionActionType.AIR_SURVEILLANCE) val result = useCase.execute(action) @@ -48,8 +52,8 @@ class GetComputeSatiTest { @Test fun `execute should merge db sati with action for control actions`() { - val useCase = GetComputeSati(satiRepo) val actionId = 761 + val useCase = GetComputeSati(satiRepo, controlResourceRepo) val action = createAction(id = 761, actionType = MissionActionType.AIR_CONTROL) val dbSati = createSatiEntity(actionId = actionId) @@ -87,11 +91,11 @@ class GetComputeSatiTest { ): SatiEntity { return SatiEntity( id = UUID.randomUUID(), - module = "AA", + module = SatiModuleType.M1, actionId = actionId.toString(), createdAt = Instant.parse("2026-03-24T10:15:30Z"), updatedAt = Instant.parse("2026-03-24T11:15:30Z"), - inspectionStartDatetimeUtc = Instant.parse("2026-03-24T09:15:30Z") + startDatetimeUtc = Instant.parse("2026-03-24T09:15:30Z") ) } } diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt index 49b93c989..15101be7a 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt @@ -1,6 +1,7 @@ package fr.gouv.gmampa.rapportnav.domain.use_cases.mission.sati import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiModuleType import fr.gouv.dgampa.rapportnav.domain.repositories.mission.sati.ISatiRepository import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.v2.ProcessSati import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.SatiMapper @@ -52,13 +53,13 @@ class ProcessSatiTest { val sati = createSati( id = UUID.randomUUID(), actionId = actionId, - module = "AA", + module = SatiModuleType.M1, actionTaken = "Checked" ) val existingInDb = createEntity( id = UUID.randomUUID(), actionId = actionId, - module = "BB" + module = SatiModuleType.M3 ) val processSati = ProcessSati(satiRepo) val entityToSave = SatiMapper.toEntity(sati) @@ -76,24 +77,21 @@ class ProcessSatiTest { private fun createSati( id: UUID? = UUID.randomUUID(), actionId: String, - module: String = "MODULE", + module: SatiModuleType = SatiModuleType.M1, actionTaken: String? = null ): Sati { return Sati( id = id, module = module, actionId = actionId, - createdAt = Instant.parse("2026-03-24T10:15:30Z"), - updatedAt = Instant.parse("2026-03-24T11:15:30Z"), - actionTaken = actionTaken, - inspectionStartDatetimeUtc = Instant.parse("2026-03-24T09:15:30Z") + startDatetimeUtc = Instant.parse("2026-03-24T09:15:30Z") ) } private fun createEntity( id: UUID? = UUID.randomUUID(), actionId: String, - module: String = "MODULE", + module: SatiModuleType = SatiModuleType.M1, actionTaken: String? = null ): SatiEntity { return SatiEntity( @@ -102,8 +100,7 @@ class ProcessSatiTest { actionId = actionId, createdAt = Instant.parse("2026-03-24T10:15:30Z"), updatedAt = Instant.parse("2026-03-24T11:15:30Z"), - actionTaken = actionTaken, - inspectionStartDatetimeUtc = Instant.parse("2026-03-24T09:15:30Z") + startDatetimeUtc = Instant.parse("2026-03-24T09:15:30Z") ) } } diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt index 8abb4d7e3..abe289ca8 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt @@ -1,217 +1,379 @@ package fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati import com.neovisionaries.i18n.CountryCode -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.AddressEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.ContactEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.ControlResource +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.LogbookMessagePurpose +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* +import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.* import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.SatiMapper import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.springframework.test.context.junit.jupiter.SpringExtension import java.time.Instant import java.util.* -@ExtendWith(SpringExtension::class) class SatiMapperTest { - @Test - fun `fromEntity should map SatiEntity to SatiResponse`() { + private val timestamp = Instant.parse("2026-03-24T09:15:30Z") + + private fun buildFullEntity(): SatiEntity { val address = AddressEntity( - id = UUID.randomUUID(), + id = 1, street = "1 rue de la mer", + fullAddress = "1 rue de la mer, 75000 Paris", zipcode = "75000", town = "Paris", country = CountryCode.FR, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + lat = 48.856789, + lng = 2.345678 ) - val contact = ContactEntity( - id = UUID.randomUUID(), + id = 2, fullName = "John Doe", - nationality = CountryCode.FR, - email = "john.doe@example.com", + firstName = "John", + lastName = "Doe", + nationality = "FRA", + email = "john@example.com", phone = "+33123456789", - address = address, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + address = address ) - - val sati = SatiEntity( + val party = SatiPartyEntity( + id = 3, + partyType = "OWNER", + comments = "some comments", + signature = true, + contact = contact + ) + val jpe = SatiJpeEntity( + pnoId = "PNO-123", + portId = "FRLEH", + portName = "Le Havre", + tripNumber = "TRIP-001", + lastStopDate = timestamp, + pnoType = LogbookMessagePurpose.LAN + ) + val vessel = SatiVesselEntity( + id = 10, + jpe = jpe, + type = "Trawler", + name = "Le Marin", + immat = "CFR-123", + imo = "IMO1234567", + length = 25.5, + extRef = "EXT-001", + ircs = "FXYZ", + owner = party, + flagState = CountryCode.FR, + charterer = SatiPartyEntity(id = 4, partyType = "CHARTERER"), + pnoType = "LAN", + tripNumber = "TRIP-001", + agent = SatiPartyEntity(id = 5, partyType = "AGENT"), + master = SatiPartyEntity(id = 6, partyType = "MASTER"), + isMasterOwner = true + ) + val inspector = SatiInspectorEntity( + id = 7, + agentId = 42, + party = SatiPartyEntity(id = 8, partyType = "INSPECTOR"), + authorityType = AuthorityType.AECP, + isOutOfUnit = false + ) + return SatiEntity( id = UUID.randomUUID(), - module = "AA", + module = SatiModuleType.M1, actionId = UUID.randomUUID().toString(), - createdAt = Instant.parse("2026-03-24T10:15:30+01:00"), - updatedAt = Instant.parse("2026-03-24T11:15:30+01:00"), - actionTaken = "Checked", - agentContact = contact, - appointingAuthority = "Prefecture", - beneficialOwnerContact = contact, - catchCertificateInfo = true, - chartererContact = contact, - commonMarketingStandards = false, - driverContact = contact, - euFishingTripId = "EU-123", - fisheryLotLabelling = true, - fisheryProductDocumentsInspection = true, - fisheryProductStorageMechanismInspected = false, - fisheryProductWeighedBeforeSale = true, - freshnessCategories = "A", - importerContact = contact, - infringementsObservations = "None", - inspectionEndDatetimeUtc = Instant.parse("2026-03-24T12:15:30+01:00"), - inspectionStartDatetimeUtc = Instant.parse("2026-03-24T09:15:30+01:00"), - inspectionLocationAddressId = UUID.randomUUID().toString(), - inspectorComments = "OK", - inspectorNameOrId = "Inspector 1", - inspectorSignature = true, - inspectorUniqueId = "INS-1", - landingDeclarationInfo = false, - lotIdSpeciesQuantities = """{"a":1}""", - lotsArt58Compliance = true, - marketOwnerContact = contact, - marketOwnerRepresentativeContact = contact, - marketPremisesAddressId = UUID.randomUUID().toString(), - marketPremisesLocation = "Port", - marketPremisesName = "Market A", - masterComments = "Master note", - masterContact = contact, - newLotCompositionInfo = """{"b":2}""", - operatorComments = "Operator note", - operatorSignature = false, - patrolVesselExternalMarking = "EXT-1", - patrolVesselRadioCallSign = "CALL-1", - registeredBuyerContact = contact, - responsiblePersonSignature = true, - sizeGradingCategories = "S", - supplierInvoiceInfo = true, - takeOverDeclarationInfo = false, - traceabilityRecordingSystemArt58 = true, - traceabilitySupplierIdentificationSystem = false, - tractorRegistrationNumber = "TR-1", - trailerRegistrationNumber = "TL-1", - transportDocumentInfo = true, - transporterComments = "Transport note", - transporterSignature = false, - useOfUndersizedFisheryProducts = true, - vehicleNationality = CountryCode.FR, - vehicleOwnerContact = contact, - vehicleType = "Truck", - vesselOwnerContact = contact + resource = ControlResourceEntity(id = 99, name = "Vedette A"), + vessel = vessel, + createdAt = timestamp, + updatedAt = timestamp, + startDatetimeUtc = timestamp, + endDatetimeUtc = timestamp.plusSeconds(3600), + inspectors = listOf(inspector) ) - - val response = SatiMapper.fromEntity(sati) - - assertThat(response.id).isEqualTo(sati.id) - assertThat(response.module).isEqualTo(sati.module) - assertThat(response.actionId).isEqualTo(sati.actionId) - assertThat(response.createdAt).isEqualTo(sati.createdAt) - assertThat(response.updatedAt).isEqualTo(sati.updatedAt) - assertThat(response.actionTaken).isEqualTo(sati.actionTaken) - assertThat(response.agentContact?.id).isEqualTo(contact.id) - assertThat(response.agentContact?.address?.id).isEqualTo(address.id) - assertThat(response.appointingAuthority).isEqualTo(sati.appointingAuthority) - assertThat(response.catchCertificateInfo).isEqualTo(true) - assertThat(response.vehicleType).isEqualTo("Truck") - assertThat(response.vesselOwnerContact?.email).isEqualTo(contact.email) } - @Test - fun `toEntity should map SatiResponse to SatiEntity`() { + private fun buildFullSati(): Sati { val address = Address( - id = UUID.randomUUID(), + id = 1, street = "1 rue de la mer", + fullAddress = "1 rue de la mer, 75000 Paris", zipcode = "75000", town = "Paris", country = CountryCode.FR, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + lat = 48.856789, + lng = 2.345678 ) - val contact = Contact( - id = UUID.randomUUID(), + id = 2, fullName = "John Doe", - nationality = CountryCode.FR, - email = "john.doe@example.com", + firstName = "John", + lastName = "Doe", + nationality = "FRA", + email = "john@example.com", phone = "+33123456789", - address = address, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + address = address ) - - val response = Sati( + val party = SatiParty( + id = 3, + partyType = "OWNER", + comments = "some comments", + signature = true, + contact = contact + ) + val jpe = SatiJpe( + pnoId = "PNO-123", + portId = "FRLEH", + portName = "Le Havre", + tripNumber = "TRIP-001", + lastStopDate = timestamp, + pnoType = LogbookMessagePurpose.LAN + ) + val vessel = SatiVessel( + id = 10, + jpe = jpe, + type = "Trawler", + name = "Le Marin", + immat = "CFR-123", + imo = "IMO1234567", + length = 25.5, + extRef = "EXT-001", + ircs = "FXYZ", + owner = party, + flagState = CountryCode.FR, + charterer = SatiParty(id = 4, partyType = "CHARTERER"), + pnoType = "LAN", + tripNumber = "TRIP-001", + agent = SatiParty(id = 5, partyType = "AGENT"), + master = SatiParty(id = 6, partyType = "MASTER"), + isMasterOwner = true + ) + val inspector = SatiInspector( + id = 7, + agentId = 42, + party = SatiParty(id = 8, partyType = "INSPECTOR"), + authorityType = AuthorityType.AECP, + isOutOfUnit = false + ) + return Sati( id = UUID.randomUUID(), - module = "AA", actionId = UUID.randomUUID().toString(), - createdAt = Instant.parse("2026-03-24T10:15:30+01:00"), - updatedAt = Instant.parse("2026-03-24T11:15:30+01:00"), - actionTaken = "Checked", - agentContact = contact, - appointingAuthority = "Prefecture", - beneficialOwnerContact = contact, - catchCertificateInfo = true, - chartererContact = contact, - commonMarketingStandards = false, - driverContact = contact, - euFishingTripId = "EU-123", - fisheryLotLabelling = true, - fisheryProductDocumentsInspection = true, - fisheryProductStorageMechanismInspected = false, - fisheryProductWeighedBeforeSale = true, - freshnessCategories = "A", - importerContact = contact, - infringementsObservations = "None", - inspectionEndDatetimeUtc = Instant.parse("2026-03-24T12:15:30+01:00"), - inspectionStartDatetimeUtc = Instant.parse("2026-03-24T09:15:30+01:00"), - inspectionLocationAddressId = UUID.randomUUID().toString(), - inspectorComments = "OK", - inspectorNameOrId = "Inspector 1", - inspectorSignature = true, - inspectorUniqueId = "INS-1", - landingDeclarationInfo = false, - lotIdSpeciesQuantities = """{"a":1}""", - lotsArt58Compliance = true, - marketOwnerContact = contact, - marketOwnerRepresentativeContact = contact, - marketPremisesAddressId = UUID.randomUUID().toString(), - marketPremisesLocation = "Port", - marketPremisesName = "Market A", - masterComments = "Master note", - masterContact = contact, - newLotCompositionInfo = """{"b":2}""", - operatorComments = "Operator note", - operatorSignature = false, - patrolVesselExternalMarking = "EXT-1", - patrolVesselRadioCallSign = "CALL-1", - registeredBuyerContact = contact, - responsiblePersonSignature = true, - sizeGradingCategories = "S", - supplierInvoiceInfo = true, - takeOverDeclarationInfo = false, - traceabilityRecordingSystemArt58 = true, - traceabilitySupplierIdentificationSystem = false, - tractorRegistrationNumber = "TR-1", - trailerRegistrationNumber = "TL-1", - transportDocumentInfo = true, - transporterComments = "Transport note", - transporterSignature = false, - useOfUndersizedFisheryProducts = true, - vehicleNationality = CountryCode.FR, - vehicleOwnerContact = contact, - vehicleType = "Truck", - vesselOwnerContact = contact + module = SatiModuleType.M1, + resource = ControlResource(id = 99, name = "Vedette A"), + vessel = vessel, + startDatetimeUtc = timestamp, + endDatetimeUtc = timestamp.plusSeconds(3600), + inspectors = listOf(inspector) ) + } + + @Nested + inner class FromEntity { + + @Test + fun `should map all top-level fields`() { + val entity = buildFullEntity() + val result = SatiMapper.fromEntity(entity) + + assertThat(result.id).isEqualTo(entity.id) + assertThat(result.actionId).isEqualTo(entity.actionId) + assertThat(result.module).isEqualTo(entity.module) + assertThat(result.startDatetimeUtc).isEqualTo(entity.startDatetimeUtc) + assertThat(result.endDatetimeUtc).isEqualTo(entity.endDatetimeUtc) + } + + @Test + fun `should map vessel fields`() { + val entity = buildFullEntity() + val result = SatiMapper.fromEntity(entity) + + assertThat(result.vessel).isNotNull + assertThat(result.vessel?.name).isEqualTo("Le Marin") + assertThat(result.vessel?.immat).isEqualTo("CFR-123") + assertThat(result.vessel?.imo).isEqualTo("IMO1234567") + assertThat(result.vessel?.ircs).isEqualTo("FXYZ") + assertThat(result.vessel?.length).isEqualTo(25.5) + assertThat(result.vessel?.type).isEqualTo("Trawler") + assertThat(result.vessel?.extRef).isEqualTo("EXT-001") + assertThat(result.vessel?.flagState).isEqualTo(CountryCode.FR) + assertThat(result.vessel?.isMasterOwner).isTrue() + assertThat(result.vessel?.pnoType).isEqualTo("LAN") + assertThat(result.vessel?.tripNumber).isEqualTo("TRIP-001") + } + + @Test + fun `should map vessel jpe`() { + val entity = buildFullEntity() + val result = SatiMapper.fromEntity(entity) + + assertThat(result.vessel?.jpe?.pnoId).isEqualTo("PNO-123") + assertThat(result.vessel?.jpe?.portId).isEqualTo("FRLEH") + assertThat(result.vessel?.jpe?.portName).isEqualTo("Le Havre") + assertThat(result.vessel?.jpe?.tripNumber).isEqualTo("TRIP-001") + assertThat(result.vessel?.jpe?.pnoType).isEqualTo(LogbookMessagePurpose.LAN) + } + + @Test + fun `should map vessel owner with contact and address`() { + val entity = buildFullEntity() + val result = SatiMapper.fromEntity(entity) + + val owner = result.vessel?.owner + assertThat(owner?.partyType).isEqualTo("OWNER") + assertThat(owner?.comments).isEqualTo("some comments") + assertThat(owner?.signature).isTrue() + assertThat(owner?.contact?.fullName).isEqualTo("John Doe") + assertThat(owner?.contact?.email).isEqualTo("john@example.com") + assertThat(owner?.contact?.phone).isEqualTo("+33123456789") + assertThat(owner?.contact?.address?.street).isEqualTo("1 rue de la mer") + assertThat(owner?.contact?.address?.town).isEqualTo("Paris") + assertThat(owner?.contact?.address?.country).isEqualTo(CountryCode.FR) + } + + @Test + fun `should map resource`() { + val entity = buildFullEntity() + val result = SatiMapper.fromEntity(entity) + + assertThat(result.resource?.id).isEqualTo(99) + assertThat(result.resource?.name).isEqualTo("Vedette A") + } + + @Test + fun `should map inspectors`() { + val entity = buildFullEntity() + val result = SatiMapper.fromEntity(entity) + + assertThat(result.inspectors).hasSize(1) + val inspector = result.inspectors?.first() + assertThat(inspector?.id).isEqualTo(7) + assertThat(inspector?.agentId).isEqualTo(42) + assertThat(inspector?.authorityType).isEqualTo(AuthorityType.AECP) + assertThat(inspector?.isOutOfUnit).isFalse() + assertThat(inspector?.party?.partyType).isEqualTo("INSPECTOR") + } + + @Test + fun `should handle null entity fields gracefully`() { + val result = SatiMapper.fromEntity(null) + + assertThat(result.id).isNull() + assertThat(result.vessel).isNull() + assertThat(result.actionId).isEmpty() + assertThat(result.module).isEqualTo(SatiModuleType.M1) + assertThat(result.inspectors).isEmpty() + } + + @Test + fun `should default module to M1 when entity module is null`() { + val entity = buildFullEntity().copy(module = SatiModuleType.M1) + val result = SatiMapper.fromEntity(entity) + assertThat(result.module).isEqualTo(SatiModuleType.M1) + } + } + + @Nested + inner class ToEntity { + + @Test + fun `should map all top-level fields`() { + val sati = buildFullSati() + val result = SatiMapper.toEntity(sati) + + assertThat(result.id).isEqualTo(sati.id) + assertThat(result.actionId).isEqualTo(sati.actionId) + assertThat(result.module).isEqualTo(sati.module) + assertThat(result.startDatetimeUtc).isEqualTo(sati.startDatetimeUtc) + assertThat(result.endDatetimeUtc).isEqualTo(sati.endDatetimeUtc) + } + + @Test + fun `should map vessel fields`() { + val sati = buildFullSati() + val result = SatiMapper.toEntity(sati) + + assertThat(result.vessel).isNotNull + assertThat(result.vessel?.name).isEqualTo("Le Marin") + assertThat(result.vessel?.immat).isEqualTo("CFR-123") + assertThat(result.vessel?.imo).isEqualTo("IMO1234567") + assertThat(result.vessel?.ircs).isEqualTo("FXYZ") + assertThat(result.vessel?.length).isEqualTo(25.5) + assertThat(result.vessel?.flagState).isEqualTo(CountryCode.FR) + assertThat(result.vessel?.isMasterOwner).isTrue() + } + + @Test + fun `should map vessel parties`() { + val sati = buildFullSati() + val result = SatiMapper.toEntity(sati) + + assertThat(result.vessel?.owner?.partyType).isEqualTo("OWNER") + assertThat(result.vessel?.charterer?.partyType).isEqualTo("CHARTERER") + assertThat(result.vessel?.agent?.partyType).isEqualTo("AGENT") + assertThat(result.vessel?.master?.partyType).isEqualTo("MASTER") + } + + @Test + fun `should map contact address`() { + val sati = buildFullSati() + val result = SatiMapper.toEntity(sati) + + val address = result.vessel?.owner?.contact?.address + assertThat(address?.street).isEqualTo("1 rue de la mer") + assertThat(address?.zipcode).isEqualTo("75000") + assertThat(address?.town).isEqualTo("Paris") + assertThat(address?.country).isEqualTo(CountryCode.FR) + assertThat(address?.lat).isEqualTo(48.856789) + assertThat(address?.lng).isEqualTo(2.345678) + } + + @Test + fun `should map inspectors`() { + val sati = buildFullSati() + val result = SatiMapper.toEntity(sati) + + assertThat(result.inspectors).hasSize(1) + val inspector = result.inspectors?.first() + assertThat(inspector?.agentId).isEqualTo(42) + assertThat(inspector?.authorityType).isEqualTo(AuthorityType.AECP) + assertThat(inspector?.isOutOfUnit).isFalse() + } + + @Test + fun `should map resource`() { + val sati = buildFullSati() + val result = SatiMapper.toEntity(sati) + + assertThat(result.resource?.id).isEqualTo(99) + assertThat(result.resource?.name).isEqualTo("Vedette A") + } + + @Test + fun `should handle null response`() { + val result = SatiMapper.toEntity(null) + + assertThat(result.id).isNull() + assertThat(result.vessel).isNull() + assertThat(result.actionId).isEmpty() + assertThat(result.module).isEqualTo(SatiModuleType.M1) + assertThat(result.inspectors).isEmpty() + } + } + + @Nested + inner class RoundTrip { + + @Test + fun `fromEntity then toEntity should preserve key fields`() { + val entity = buildFullEntity() + val roundTripped = SatiMapper.toEntity(SatiMapper.fromEntity(entity)) - val entity = SatiMapper.toEntity(response) - - assertThat(entity.id).isEqualTo(response.id) - assertThat(entity.module).isEqualTo(response.module) - assertThat(entity.actionId).isEqualTo(response.actionId) - assertThat(entity.createdAt).isEqualTo(response.createdAt) - assertThat(entity.updatedAt).isEqualTo(response.updatedAt) - assertThat(entity.actionTaken).isEqualTo(response.actionTaken) - assertThat(entity.agentContact?.id).isEqualTo(contact.id) - assertThat(entity.agentContact?.address?.id).isEqualTo(address.id) - assertThat(entity.appointingAuthority).isEqualTo(response.appointingAuthority) - assertThat(entity.catchCertificateInfo).isEqualTo(true) - assertThat(entity.vehicleType).isEqualTo("Truck") - assertThat(entity.vesselOwnerContact?.email).isEqualTo(contact.email) + assertThat(roundTripped.id).isEqualTo(entity.id) + assertThat(roundTripped.actionId).isEqualTo(entity.actionId) + assertThat(roundTripped.module).isEqualTo(entity.module) + assertThat(roundTripped.vessel?.name).isEqualTo(entity.vessel?.name) + assertThat(roundTripped.vessel?.immat).isEqualTo(entity.vessel?.immat) + assertThat(roundTripped.vessel?.owner?.contact?.fullName).isEqualTo(entity.vessel?.owner?.contact?.fullName) + } } } diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt index 68a2c9c8b..f1111b24c 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt @@ -1,221 +1,373 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.sati import com.neovisionaries.i18n.CountryCode -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.AddressEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.ContactEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity -import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.AddressModel -import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.ContactModel -import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.SatiModel -import fr.gouv.dgampa.rapportnav.infrastructure.database.repositories.mission.sati.SatiModelMapper +import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* +import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.* import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.time.Instant -import java.util.UUID +import java.util.* class SatiModelMapperTest { - @Test - fun `toEntity should map SatiModel to SatiEntity`() { - val addressModel = AddressModel( - id = UUID.randomUUID(), + private val timestamp = Instant.parse("2026-03-24T09:15:30Z") + + private fun buildAddressModel(): AddressModel { + return AddressModel( + id = 1, street = "1 rue de la mer", + fullAddress = "1 rue de la mer, 75000 Paris", zipcode = "75000", town = "Paris", country = "FRA", lng = 2.345678, lat = 48.856789, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + createdAt = timestamp, + updatedAt = timestamp ) + } - val contactModel = ContactModel( - id = UUID.randomUUID(), + private fun buildContactModel(address: AddressModel? = null): ContactModel { + return ContactModel( + id = 2, fullName = "John Doe", + firstName = "John", + lastName = "Doe", nationality = "FRA", - email = "john.doe@example.com", + email = "john@example.com", phone = "+33123456789", - address = addressModel, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + address = address, + createdAt = timestamp, + updatedAt = timestamp ) + } - val model = SatiModel( - id = UUID.randomUUID(), - module = "AA", - actionId = UUID.randomUUID().toString(), - createdAt = Instant.parse("2026-03-24T10:15:30+01:00"), - updatedAt = Instant.parse("2026-03-24T11:15:30+01:00"), - actionTaken = "Checked", - agentContact = contactModel, - appointingAuthority = "Prefecture", - beneficialOwnerContact = contactModel, - catchCertificateInfo = true, - chartererContact = contactModel, - commonMarketingStandards = false, - driverContact = contactModel, - euFishingTripId = "EU-123", - fisheryLotLabelling = true, - fisheryProductDocumentsInspection = true, - fisheryProductStorageMechanismInspected = false, - fisheryProductWeighedBeforeSale = true, - freshnessCategories = "A", - importerContact = contactModel, - infringementsObservations = "None", - inspectionEndDatetimeUtc = Instant.parse("2026-03-24T12:15:30+01:00"), - inspectionStartDatetimeUtc = Instant.parse("2026-03-24T09:15:30+01:00"), - inspectionLocationAddressId = UUID.randomUUID().toString(), - inspectorComments = "OK", - inspectorNameOrId = "Inspector 1", - inspectorSignature = true, - inspectorUniqueId = "INS-1", - landingDeclarationInfo = false, - lotIdSpeciesQuantities = """{"a":1}""", - lotsArt58Compliance = true, - marketOwnerContact = contactModel, - marketOwnerRepresentativeContact = contactModel, - marketPremisesAddressId = UUID.randomUUID().toString(), - marketPremisesLocation = "Port", - marketPremisesName = "Market A", - masterComments = "Master note", - masterContact = contactModel, - newLotCompositionInfo = """{"b":2}""", - operatorComments = "Operator note", - operatorSignature = false, - patrolVesselExternalMarking = "EXT-1", - patrolVesselRadioCallSign = "CALL-1", - registeredBuyerContact = contactModel, - responsiblePersonSignature = true, - sizeGradingCategories = "S", - supplierInvoiceInfo = true, - takeOverDeclarationInfo = false, - traceabilityRecordingSystemArt58 = true, - traceabilitySupplierIdentificationSystem = false, - tractorRegistrationNumber = "TR-1", - trailerRegistrationNumber = "TL-1", - transportDocumentInfo = true, - transporterComments = "Transport note", - transporterSignature = false, - useOfUndersizedFisheryProducts = true, - vehicleNationality = "FRA", - vehicleOwnerContact = contactModel, - vehicleType = "Truck", - vesselOwnerContact = contactModel + private fun buildPartyModel(type: String = "OWNER", contact: ContactModel? = null): SatiPartyModel { + return SatiPartyModel( + id = 3, + partyType = type, + comments = "some comments", + signature = true, + contact = contact, + createdAt = timestamp, + updatedAt = timestamp ) + } - val entity = SatiModelMapper.toEntity(model) - - assertThat(entity.id).isEqualTo(model.id) - assertThat(entity.module).isEqualTo(model.module) - assertThat(entity.actionId).isEqualTo(model.actionId) - assertThat(entity.createdAt).isEqualTo(model.createdAt) - assertThat(entity.updatedAt).isEqualTo(model.updatedAt) - assertThat(entity.actionTaken).isEqualTo(model.actionTaken) - assertThat(entity.agentContact?.id).isEqualTo(contactModel.id) - assertThat(entity.agentContact?.address?.id).isEqualTo(addressModel.id) - assertThat(entity.appointingAuthority).isEqualTo(model.appointingAuthority) - assertThat(entity.catchCertificateInfo).isEqualTo(true) - assertThat(entity.vehicleType).isEqualTo("Truck") - assertThat(entity.vesselOwnerContact?.email).isEqualTo(contactModel.email) + private fun buildInspectorModel(party: SatiPartyModel? = null): SatiInspectorModel { + return SatiInspectorModel( + id = 7, + agentId = 42, + party = party, + authorityType = "AECP", + isOutOfUnit = false, + createdAt = timestamp, + updatedAt = timestamp + ) + } + + private fun buildVesselModel( + agent: SatiPartyModel? = null, + master: SatiPartyModel? = null + ): SatiVesselModel { + return SatiVesselModel( + id = 10, + pnoType = "LAN", + tripNumber = "TRIP-001", + agent = agent, + master = master, + isMasterOwner = true, + createdAt = timestamp, + updatedAt = timestamp + ) } - @Test - fun `toModel should map SatiEntity to SatiModel`() { - val addressEntity = AddressEntity( + private fun buildFullModel(): SatiModel { + val address = buildAddressModel() + val contact = buildContactModel(address) + val agentParty = buildPartyModel("AGENT", contact) + val masterParty = buildPartyModel("MASTER") + val vessel = buildVesselModel(agent = agentParty, master = masterParty) + val inspectorParty = buildPartyModel("INSPECTOR") + val inspector = buildInspectorModel(inspectorParty) + + return SatiModel( id = UUID.randomUUID(), + module = "M1", + actionId = UUID.randomUUID().toString(), + resourceId = 99, + vessel = vessel, + inspectors = mutableListOf(inspector), + createdAt = timestamp, + updatedAt = timestamp + ) + } + + private fun buildFullEntity(): SatiEntity { + val address = AddressEntity( + id = 1, street = "1 rue de la mer", + fullAddress = "1 rue de la mer, 75000 Paris", zipcode = "75000", town = "Paris", country = CountryCode.FR, - lng = 2.345678, lat = 48.856789, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + lng = 2.345678, + createdAt = timestamp, + updatedAt = timestamp ) - - val contactEntity = ContactEntity( - id = UUID.randomUUID(), + val contact = ContactEntity( + id = 2, fullName = "John Doe", - nationality = CountryCode.FR, - email = "john.doe@example.com", + firstName = "John", + lastName = "Doe", + nationality = "FRA", + email = "john@example.com", phone = "+33123456789", - address = addressEntity, - createdAt = Instant.parse("2026-03-24T10:15:30+01:00") + address = address, + createdAt = timestamp, + updatedAt = timestamp ) - - val entity = SatiEntity( + val agentParty = SatiPartyEntity( + id = 3, + partyType = "AGENT", + comments = "some comments", + signature = true, + contact = contact, + createdAt = timestamp, + updatedAt = timestamp + ) + val masterParty = SatiPartyEntity(id = 6, partyType = "MASTER", createdAt = timestamp, updatedAt = timestamp) + val vessel = SatiVesselEntity( + id = 10, + pnoType = "LAN", + tripNumber = "TRIP-001", + agent = agentParty, + master = masterParty, + isMasterOwner = true, + createdAt = timestamp, + updatedAt = timestamp + ) + val inspector = SatiInspectorEntity( + id = 7, + agentId = 42, + party = SatiPartyEntity(id = 8, partyType = "INSPECTOR", createdAt = timestamp, updatedAt = timestamp), + authorityType = AuthorityType.AECP, + isOutOfUnit = false, + createdAt = timestamp, + updatedAt = timestamp + ) + return SatiEntity( id = UUID.randomUUID(), - module = "AA", + module = SatiModuleType.M1, actionId = UUID.randomUUID().toString(), - createdAt = Instant.parse("2026-03-24T10:15:30+01:00"), - updatedAt = Instant.parse("2026-03-24T11:15:30+01:00"), - actionTaken = "Checked", - agentContact = contactEntity, - appointingAuthority = "Prefecture", - beneficialOwnerContact = contactEntity, - catchCertificateInfo = true, - chartererContact = contactEntity, - commonMarketingStandards = false, - driverContact = contactEntity, - euFishingTripId = "EU-123", - fisheryLotLabelling = true, - fisheryProductDocumentsInspection = true, - fisheryProductStorageMechanismInspected = false, - fisheryProductWeighedBeforeSale = true, - freshnessCategories = "A", - importerContact = contactEntity, - infringementsObservations = "None", - inspectionEndDatetimeUtc = Instant.parse("2026-03-24T12:15:30+01:00"), - inspectionStartDatetimeUtc = Instant.parse("2026-03-24T09:15:30+01:00"), - inspectionLocationAddressId = UUID.randomUUID().toString(), - inspectorComments = "OK", - inspectorNameOrId = "Inspector 1", - inspectorSignature = true, - inspectorUniqueId = "INS-1", - landingDeclarationInfo = false, - lotIdSpeciesQuantities = """{"a":1}""", - lotsArt58Compliance = true, - marketOwnerContact = contactEntity, - marketOwnerRepresentativeContact = contactEntity, - marketPremisesAddressId = UUID.randomUUID().toString(), - marketPremisesLocation = "Port", - marketPremisesName = "Market A", - masterComments = "Master note", - masterContact = contactEntity, - newLotCompositionInfo = """{"b":2}""", - operatorComments = "Operator note", - operatorSignature = false, - patrolVesselExternalMarking = "EXT-1", - patrolVesselRadioCallSign = "CALL-1", - registeredBuyerContact = contactEntity, - responsiblePersonSignature = true, - sizeGradingCategories = "S", - supplierInvoiceInfo = true, - takeOverDeclarationInfo = false, - traceabilityRecordingSystemArt58 = true, - traceabilitySupplierIdentificationSystem = false, - tractorRegistrationNumber = "TR-1", - trailerRegistrationNumber = "TL-1", - transportDocumentInfo = true, - transporterComments = "Transport note", - transporterSignature = false, - useOfUndersizedFisheryProducts = true, - vehicleNationality = CountryCode.FR, - vehicleOwnerContact = contactEntity, - vehicleType = "Truck", - vesselOwnerContact = contactEntity + resource = ControlResourceEntity(id = 99), + vessel = vessel, + createdAt = timestamp, + updatedAt = timestamp, + inspectors = listOf(inspector) ) + } + + @Nested + inner class ToEntity { + + @Test + fun `should map top-level fields`() { + val model = buildFullModel() + val entity = SatiModelMapper.toEntity(model) + + assertThat(entity.id).isEqualTo(model.id) + assertThat(entity.actionId).isEqualTo(model.actionId) + assertThat(entity.module).isEqualTo(SatiModuleType.M1) + assertThat(entity.createdAt).isEqualTo(model.createdAt) + assertThat(entity.updatedAt).isEqualTo(model.updatedAt) + } + + @Test + fun `should map resource id`() { + val model = buildFullModel() + val entity = SatiModelMapper.toEntity(model) + + assertThat(entity.resource?.id).isEqualTo(99) + } + + @Test + fun `should map vessel fields`() { + val model = buildFullModel() + val entity = SatiModelMapper.toEntity(model) + + assertThat(entity.vessel).isNotNull + assertThat(entity.vessel?.pnoType).isEqualTo("LAN") + assertThat(entity.vessel?.tripNumber).isEqualTo("TRIP-001") + assertThat(entity.vessel?.isMasterOwner).isTrue() + } + + @Test + fun `should map vessel agent party with nested contact and address`() { + val model = buildFullModel() + val entity = SatiModelMapper.toEntity(model) + + val agent = entity.vessel?.agent + assertThat(agent?.partyType).isEqualTo("AGENT") + assertThat(agent?.comments).isEqualTo("some comments") + assertThat(agent?.signature).isTrue() + assertThat(agent?.contact?.fullName).isEqualTo("John Doe") + assertThat(agent?.contact?.email).isEqualTo("john@example.com") + assertThat(agent?.contact?.address?.street).isEqualTo("1 rue de la mer") + assertThat(agent?.contact?.address?.town).isEqualTo("Paris") + assertThat(agent?.contact?.address?.country).isEqualTo(CountryCode.FR) + } + + @Test + fun `should map inspectors with authority type`() { + val model = buildFullModel() + val entity = SatiModelMapper.toEntity(model) + + assertThat(entity.inspectors).hasSize(1) + val inspector = entity.inspectors?.first() + assertThat(inspector?.id).isEqualTo(7) + assertThat(inspector?.agentId).isEqualTo(42) + assertThat(inspector?.authorityType).isEqualTo(AuthorityType.AECP) + assertThat(inspector?.isOutOfUnit).isFalse() + } + + @Test + fun `should handle null vessel`() { + val model = SatiModel( + module = "M1", + actionId = "action-1", + vessel = null + ) + val entity = SatiModelMapper.toEntity(model) + assertThat(entity.vessel).isNull() + } + + @Test + fun `should handle empty inspectors`() { + val model = SatiModel( + module = "M1", + actionId = "action-1", + inspectors = mutableListOf() + ) + val entity = SatiModelMapper.toEntity(model) + assertThat(entity.inspectors).isEmpty() + } + } + + @Nested + inner class ToModel { + + @Test + fun `should map top-level fields`() { + val entity = buildFullEntity() + val model = SatiModelMapper.toModel(entity) + + assertThat(model.id).isEqualTo(entity.id) + assertThat(model.actionId).isEqualTo(entity.actionId) + assertThat(model.module).isEqualTo("M1") + assertThat(model.createdAt).isEqualTo(entity.createdAt) + assertThat(model.updatedAt).isEqualTo(entity.updatedAt) + } + + @Test + fun `should map resource id from entity`() { + val entity = buildFullEntity() + val model = SatiModelMapper.toModel(entity) + + assertThat(model.resourceId).isEqualTo(99) + } + + @Test + fun `should map vessel fields`() { + val entity = buildFullEntity() + val model = SatiModelMapper.toModel(entity) + + assertThat(model.vessel).isNotNull + assertThat(model.vessel?.pnoType).isEqualTo("LAN") + assertThat(model.vessel?.tripNumber).isEqualTo("TRIP-001") + assertThat(model.vessel?.isMasterOwner).isTrue() + } + + @Test + fun `should map vessel agent party with nested contact and address`() { + val entity = buildFullEntity() + val model = SatiModelMapper.toModel(entity) + + val agent = model.vessel?.agent + assertThat(agent?.partyType).isEqualTo("AGENT") + assertThat(agent?.comments).isEqualTo("some comments") + assertThat(agent?.signature).isTrue() + assertThat(agent?.contact?.fullName).isEqualTo("John Doe") + assertThat(agent?.contact?.email).isEqualTo("john@example.com") + assertThat(agent?.contact?.address?.street).isEqualTo("1 rue de la mer") + assertThat(agent?.contact?.address?.town).isEqualTo("Paris") + assertThat(agent?.contact?.address?.country).isEqualTo("FRA") + } + + @Test + fun `should map inspectors with authority type as string`() { + val entity = buildFullEntity() + val model = SatiModelMapper.toModel(entity) + + assertThat(model.inspectors).hasSize(1) + val inspector = model.inspectors.first() + assertThat(inspector.id).isEqualTo(7) + assertThat(inspector.agentId).isEqualTo(42) + assertThat(inspector.authorityType).isEqualTo("AECP") + assertThat(inspector.isOutOfUnit).isFalse() + } + + @Test + fun `should handle null vessel`() { + val entity = SatiEntity( + module = SatiModuleType.M1, + actionId = "action-1", + vessel = null + ) + val model = SatiModelMapper.toModel(entity) + assertThat(model.vessel).isNull() + } + + @Test + fun `should handle null inspectors`() { + val entity = SatiEntity( + module = SatiModuleType.M1, + actionId = "action-1", + inspectors = null + ) + val model = SatiModelMapper.toModel(entity) + assertThat(model.inspectors).isEmpty() + } + + @Test + fun `should handle null resource`() { + val entity = SatiEntity( + module = SatiModuleType.M1, + actionId = "action-1", + resource = null + ) + val model = SatiModelMapper.toModel(entity) + assertThat(model.resourceId).isNull() + } + } + + @Nested + inner class RoundTrip { + + @Test + fun `toModel then toEntity should preserve key fields`() { + val entity = buildFullEntity() + val roundTripped = SatiModelMapper.toEntity(SatiModelMapper.toModel(entity)) - val model = SatiModelMapper.toModel(entity) - - assertThat(model.id).isEqualTo(entity.id) - assertThat(model.module).isEqualTo(entity.module) - assertThat(model.actionId).isEqualTo(entity.actionId) - assertThat(model.createdAt).isEqualTo(entity.createdAt) - assertThat(model.updatedAt).isEqualTo(entity.updatedAt) - assertThat(model.actionTaken).isEqualTo(entity.actionTaken) - assertThat(model.agentContact?.id).isEqualTo(contactEntity.id) - assertThat(model.agentContact?.address?.id).isEqualTo(addressEntity.id) - assertThat(model.appointingAuthority).isEqualTo(entity.appointingAuthority) - assertThat(model.catchCertificateInfo).isEqualTo(true) - assertThat(model.vehicleType).isEqualTo("Truck") - assertThat(model.vesselOwnerContact?.email).isEqualTo(contactEntity.email) + assertThat(roundTripped.id).isEqualTo(entity.id) + assertThat(roundTripped.actionId).isEqualTo(entity.actionId) + assertThat(roundTripped.module).isEqualTo(entity.module) + assertThat(roundTripped.vessel?.pnoType).isEqualTo(entity.vessel?.pnoType) + assertThat(roundTripped.vessel?.tripNumber).isEqualTo(entity.vessel?.tripNumber) + assertThat(roundTripped.vessel?.isMasterOwner).isEqualTo(entity.vessel?.isMasterOwner) + } } } diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt index 5124650ab..4b7824d55 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt @@ -1,6 +1,7 @@ package fr.gouv.gmampa.rapportnav.infrastructure.database.repositories.mission.sati import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiModuleType import fr.gouv.dgampa.rapportnav.domain.exceptions.BackendInternalException import fr.gouv.dgampa.rapportnav.domain.exceptions.BackendUsageException import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.SatiModel @@ -36,15 +37,13 @@ class JPASatiRepositoryTest { private val satiModel = SatiModel( id = satiId, module = "T1", - actionId = actionId, - inspectionStartDatetimeUtc = now + actionId = actionId ) private val satiEntity = SatiEntity( id = satiId, - module = "T1", - actionId = actionId, - inspectionStartDatetimeUtc = now + module = SatiModuleType.M1, + actionId = actionId ) @BeforeEach From cd98bbc8a3257c18247b9606205a80b22be4bbf3 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 25 Jun 2026 10:09:41 +0200 Subject: [PATCH 2/4] fix(backend): test --- .../repositories/mission/sati/JPASatiRepositoryTest.kt | 6 +----- .../common/hooks/__tests__/use-control-check.test.tsx | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt index 4b7824d55..92d4e8048 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/database/repositories/mission/sati/JPASatiRepositoryTest.kt @@ -36,7 +36,7 @@ class JPASatiRepositoryTest { private val satiModel = SatiModel( id = satiId, - module = "T1", + module = "M1", actionId = actionId ) @@ -51,8 +51,6 @@ class JPASatiRepositoryTest { jpaSatiRepository = JPASatiRepository(dbRepo) } - // --- findById --- - @Test fun `findById should return entity when found`() { `when`(dbRepo.findById(satiId)).thenReturn(Optional.of(satiModel)) @@ -83,7 +81,6 @@ class JPASatiRepositoryTest { assertThat(exception.message).contains("findById") } - // --- findByActionId --- @Test fun `findByActionId should return entity when found`() { @@ -115,7 +112,6 @@ class JPASatiRepositoryTest { assertThat(exception.message).contains("findByOwnerId") } - // --- findAll --- @Test fun `findAll should return list of entities`() { diff --git a/frontend/src/v2/features/common/hooks/__tests__/use-control-check.test.tsx b/frontend/src/v2/features/common/hooks/__tests__/use-control-check.test.tsx index 1946ef37b..1715b71a8 100644 --- a/frontend/src/v2/features/common/hooks/__tests__/use-control-check.test.tsx +++ b/frontend/src/v2/features/common/hooks/__tests__/use-control-check.test.tsx @@ -18,6 +18,6 @@ describe('usecontrolCheck', () => { const { result } = renderHook(() => usecontrolCheck()) expect(result.current.getControlCheck(ControlCheck.NO)).toEqual('Non') expect(result.current.getControlCheck(ControlCheck.YES)).toEqual('Oui') - expect(result.current.getControlCheck(ControlCheck.NOT_APPLICABLE)).toEqual('Non concerné') + expect(result.current.getControlCheck(ControlCheck.NOT_APPLICABLE)).toEqual('Non contrôlé') }) }) From 939db64a18e08fa2ac751b4c74136fbdaed01c76 Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 25 Jun 2026 16:44:01 +0200 Subject: [PATCH 3/4] feat(backend): fix save issue - update mapping attribute orphan - create a enable sati flag regarding the user service --- .../use_cases/mission/action/v2/EnableSati.kt | 16 ++++++++++++++++ .../mission/action/v2/GetComputeSati.kt | 2 ++ .../domain/use_cases/mission/v2/ProcessSati.kt | 3 +++ .../database/model/mission/sati/AddressModel.kt | 4 ++-- .../database/model/mission/sati/ContactModel.kt | 7 ++++--- .../model/mission/sati/SatiInspectorModel.kt | 9 +++++---- .../database/model/mission/sati/SatiModel.kt | 16 ++++++---------- .../model/mission/sati/SatiModelMapper.kt | 9 ++------- .../model/mission/sati/SatiPartyModel.kt | 7 ++++--- .../model/mission/sati/SatiVesselModel.kt | 8 ++++---- .../src/main/resources/application.properties | 5 ++++- .../use_cases/mission/sati/GetComputeSatiTest.kt | 13 ++++++++++--- .../use_cases/mission/sati/ProcessSatiTest.kt | 14 ++++++++++---- .../api/model/sati/SatiModelMapperTest.kt | 2 -- 14 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/EnableSati.kt diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/EnableSati.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/EnableSati.kt new file mode 100644 index 000000000..b749f3fd7 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/EnableSati.kt @@ -0,0 +1,16 @@ +package fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action.v2 + +import fr.gouv.dgampa.rapportnav.config.UseCase +import fr.gouv.dgampa.rapportnav.domain.use_cases.user.GetServiceForUser +import org.springframework.beans.factory.annotation.Value + +@UseCase +class EnableSati( + private val getServiceForUser: GetServiceForUser, + @param:Value("\${sati.enabled-services}") private val serviceIds: List +) { + fun execute(): Boolean { + val serviceId = getServiceForUser.execute() + return (serviceIds.contains(serviceId?.id)) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt index 085eb3d8c..ef8d41bbf 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/action/v2/GetComputeSati.kt @@ -9,10 +9,12 @@ import fr.gouv.dgampa.rapportnav.domain.repositories.v2.controlUnitResource.IEnv @UseCase class GetComputeSati( + private val enableSati: EnableSati, private val satiRepo: ISatiRepository, private val controlResourceRepo: IEnvControlUnitResourceRepository ) { fun execute(action: MissionAction): SatiEntity? { + if (!enableSati.execute()) return null if (action.id == null) throw IllegalArgumentException() if (!action.actionType.toString().endsWith("_CONTROL")) return null var sati = satiRepo.findByActionId(actionId = action.id.toString()) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/v2/ProcessSati.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/v2/ProcessSati.kt index 0b27b62c4..b7d4420af 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/v2/ProcessSati.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/use_cases/mission/v2/ProcessSati.kt @@ -4,16 +4,19 @@ import fr.gouv.dgampa.rapportnav.config.UseCase import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntityMapper import fr.gouv.dgampa.rapportnav.domain.repositories.mission.sati.ISatiRepository +import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action.v2.EnableSati import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.SatiMapper import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Sati @UseCase class ProcessSati( + private val enableSati: EnableSati, private val satiRepo: ISatiRepository ) { fun execute(actionId: String, sati: Sati?): SatiEntity? { if (sati == null) return null + if (!enableSati.execute()) return null val entity = SatiMapper.toEntity(sati) val fromDb = satiRepo.findByActionId(actionId) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt index c86edab59..5b0b951b0 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt @@ -44,10 +44,10 @@ import java.time.Instant var lat: Double? = null, @CreatedDate - @Column(name = "created_at", nullable = false, updatable = false) + @Column(name = "created_at", nullable = true, updatable = false) var createdAt: Instant? = null, @LastModifiedDate - @Column(name = "updated_at", nullable = false) + @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt index 24fb48167..1a6309208 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt @@ -1,5 +1,6 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati +import jakarta.persistence.CascadeType import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EntityListeners @@ -43,15 +44,15 @@ class ContactModel( @Column(name = "phone", length = 50) var phone: String? = null, - @OneToOne(fetch = FetchType.LAZY, optional = true) + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true, optional = true) @JoinColumn(name = "address_id", referencedColumnName = "id") var address: AddressModel? = null, @CreatedDate - @Column(name = "created_at", nullable = false, updatable = false) + @Column(name = "created_at", nullable = true, updatable = false) var createdAt: Instant? = null, @LastModifiedDate - @Column(name = "updated_at", nullable = false) + @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt index 818c0d4d0..0e015affe 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt @@ -1,5 +1,6 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati +import jakarta.persistence.CascadeType import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EntityListeners @@ -25,7 +26,7 @@ class SatiInspectorModel( @Column(nullable = false, updatable = false) var id: Int? = null, - @OneToOne(fetch = FetchType.LAZY, optional = true) + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true, optional = true) @JoinColumn(name = "party_id", referencedColumnName = "id") var party: SatiPartyModel? = null, @@ -39,10 +40,10 @@ class SatiInspectorModel( var isOutOfUnit: Boolean = false, @CreatedDate - @Column(name = "created_at", nullable = false, updatable = false) + @Column(name = "created_at", nullable = true, updatable = false) var createdAt: Instant? = null, @LastModifiedDate - @Column(name = "updated_at", nullable = false) - var updatedAt: Instant? = null + @Column(name = "updated_at", nullable = true) + var updatedAt: Instant? = null, ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt index 4c950a4b9..a426097a4 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt @@ -6,13 +6,11 @@ import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EntityListeners import jakarta.persistence.FetchType -import jakarta.persistence.GeneratedValue import jakarta.persistence.Id import jakarta.persistence.JoinColumn import jakarta.persistence.OneToMany import jakarta.persistence.OneToOne import jakarta.persistence.Table -import org.hibernate.annotations.UuidGenerator import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener @@ -26,10 +24,8 @@ import java.util.UUID class SatiModel( @Id - @GeneratedValue - @UuidGenerator @Column(nullable = false, updatable = false) - var id: UUID? = null, + var id: UUID = UUID.randomUUID(), @Column(name = "module", nullable = false, length = 50) var module: String, @@ -55,16 +51,16 @@ class SatiModel( cascade = [CascadeType.ALL], orphanRemoval = true ) - @JoinColumn(name = "sati_id", referencedColumnName = "id") + @JoinColumn(name = "sati_id", referencedColumnName = "id", nullable = false) var inspectors: MutableList = mutableListOf(), @CreatedDate - @Column(name = "created_at", nullable = false, updatable = false) - val createdAt: Instant? = null, + @Column(name = "created_at", nullable = true, updatable = false) + var createdAt: Instant? = null, @LastModifiedDate - @Column(name = "updated_at", nullable = false) - val updatedAt: Instant? = null + @Column(name = "updated_at", nullable = true) + var updatedAt: Instant? = null ) { override fun hashCode(): Int { return Objects.hash(id) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt index dd94a5031..c962c6967 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt @@ -3,6 +3,7 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati import com.neovisionaries.i18n.CountryCode import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* +import java.util.UUID object SatiModelMapper { fun toEntity(model: SatiModel): SatiEntity { @@ -22,10 +23,8 @@ object SatiModelMapper { fun toModel(entity: SatiEntity): SatiModel { return SatiModel( - id = entity.id, + id = entity.id ?: UUID.randomUUID(), actionId = entity.actionId, - createdAt = entity.createdAt, - updatedAt = entity.updatedAt, resourceId = entity.resource?.id, vessel = entity.vessel?.toModel(), module = entity.module.toString(), @@ -56,8 +55,6 @@ object SatiModelMapper { town = town, street = street, zipcode = zipcode, - createdAt = createdAt, - updatedAt = updatedAt, fullAddress = fullAddress, country = country?.alpha3 ) @@ -86,8 +83,6 @@ object SatiModelMapper { fullName = fullName, lastName = lastName, firstName = firstName, - createdAt = createdAt, - updatedAt = updatedAt, nationality = nationality, address = address?.toModel() ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt index d6863f77f..9e86c2eee 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt @@ -1,5 +1,6 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati +import jakarta.persistence.CascadeType import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EntityListeners @@ -34,15 +35,15 @@ class SatiPartyModel( @Column(name = "signature", nullable = false) var signature: Boolean = false, - @OneToOne(fetch = FetchType.LAZY, optional = true) + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true, optional = true) @JoinColumn(name = "contact_id", referencedColumnName = "id") var contact: ContactModel? = null, @CreatedDate - @Column(name = "created_at", nullable = false, updatable = false) + @Column(name = "created_at", nullable = true, updatable = false) var createdAt: Instant? = null, @LastModifiedDate - @Column(name = "updated_at", nullable = false) + @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null ) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt index 89a6fa884..9d891fc6a 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt @@ -22,11 +22,11 @@ class SatiVesselModel( @Column(name = "trip_number", length = 50) var tripNumber: String? = null, - @OneToOne(fetch = FetchType.LAZY, optional = true) + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true, optional = true) @JoinColumn(name = "agent_id", referencedColumnName = "id") var agent: SatiPartyModel? = null, - @OneToOne(fetch = FetchType.LAZY, optional = true) + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true, optional = true) @JoinColumn(name = "master_id", referencedColumnName = "id") var master: SatiPartyModel? = null, @@ -34,10 +34,10 @@ class SatiVesselModel( var isMasterOwner: Boolean = false, @CreatedDate - @Column(name = "created_at", nullable = false, updatable = false) + @Column(name = "created_at", nullable = true, updatable = false) var createdAt: Instant? = null, @LastModifiedDate - @Column(name = "updated_at", nullable = false) + @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null ) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 56ee7dced..a4a481110 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -38,7 +38,7 @@ server.forward-headers-strategy=framework ################ ################ # CORS - default for tests, override in profile-specific properties (infra/configurations/backend/) -cors.allowed-origins=http://localhost:3000 +cors.allowed-origins=http://localhost:3000,http://localhost:5173 ################ ################ # Logging @@ -82,3 +82,6 @@ sentry.max-breadcrumbs=50 # Metabase metabase.site-url=${METABASE_SITE_URL:https://metabase.din.developpement-durable.gouv.fr} metabase.secret-key=${METABASE_SECRET_KEY:} + +#Sati +sati.enabled-services=${SATI_ENABLED_SERVICES:100,101,102} diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt index a0dd3d0f9..d7489116d 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/GetComputeSatiTest.kt @@ -5,6 +5,7 @@ import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.Missio import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionActionType import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiModuleType +import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action.v2.EnableSati import fr.gouv.dgampa.rapportnav.domain.repositories.mission.sati.ISatiRepository import fr.gouv.dgampa.rapportnav.domain.repositories.v2.controlUnitResource.IEnvControlUnitResourceRepository import org.assertj.core.api.Assertions.assertThat @@ -22,6 +23,9 @@ import java.util.* @ContextConfiguration(classes = [GetComputeSati::class]) class GetComputeSatiTest { + @MockitoBean + private lateinit var enableSati: EnableSati + @MockitoBean private lateinit var satiRepo: ISatiRepository @@ -30,7 +34,8 @@ class GetComputeSatiTest { @Test fun `execute should throw IllegalArgumentException when action id is null`() { - val useCase = GetComputeSati(satiRepo, controlResourceRepo) + whenever(enableSati.execute()).thenReturn(true) + val useCase = GetComputeSati(enableSati, satiRepo, controlResourceRepo) val action = createAction(id = null, actionType = MissionActionType.AIR_CONTROL) assertThrows(IllegalArgumentException::class.java) { useCase.execute(action) @@ -41,7 +46,8 @@ class GetComputeSatiTest { @Test fun `execute should return null when action type is not a control`() { - val useCase = GetComputeSati(satiRepo, controlResourceRepo) + whenever(enableSati.execute()).thenReturn(true) + val useCase = GetComputeSati(enableSati, satiRepo, controlResourceRepo) val action = createAction(id = 761, actionType = MissionActionType.AIR_SURVEILLANCE) val result = useCase.execute(action) @@ -52,8 +58,9 @@ class GetComputeSatiTest { @Test fun `execute should merge db sati with action for control actions`() { + whenever(enableSati.execute()).thenReturn(true) val actionId = 761 - val useCase = GetComputeSati(satiRepo, controlResourceRepo) + val useCase = GetComputeSati(enableSati, satiRepo, controlResourceRepo) val action = createAction(id = 761, actionType = MissionActionType.AIR_CONTROL) val dbSati = createSatiEntity(actionId = actionId) diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt index 15101be7a..bc8d202a6 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/use_cases/mission/sati/ProcessSatiTest.kt @@ -3,6 +3,7 @@ package fr.gouv.gmampa.rapportnav.domain.use_cases.mission.sati import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiEntity import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.SatiModuleType import fr.gouv.dgampa.rapportnav.domain.repositories.mission.sati.ISatiRepository +import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action.v2.EnableSati import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.v2.ProcessSati import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.sati.SatiMapper import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Sati @@ -22,11 +23,14 @@ import java.util.UUID class ProcessSatiTest { @MockitoBean - private lateinit var satiRepo: ISatiRepository + private lateinit var enableSati: EnableSati + + @MockitoBean + private lateinit var satiRepo: ISatiRepository @Test fun `execute should return null when sati is null`() { - val processSati = ProcessSati(satiRepo) + val processSati = ProcessSati(enableSati, satiRepo) val result = processSati.execute(actionId = "action-1", sati = null) assertThat(result).isNull() verifyNoInteractions(satiRepo) @@ -35,10 +39,11 @@ class ProcessSatiTest { @Test fun `execute should return entity without saving when incoming sati matches database value`() { val actionId = "action-1" - val processSati = ProcessSati(satiRepo) + val processSati = ProcessSati(enableSati, satiRepo) val sati = createSati(actionId = actionId) val entity = SatiMapper.toEntity(sati) + whenever(enableSati.execute()).thenReturn(true) whenever(satiRepo.findByActionId(actionId)).thenReturn(entity) val result = processSati.execute(actionId = actionId, sati = sati) @@ -61,9 +66,10 @@ class ProcessSatiTest { actionId = actionId, module = SatiModuleType.M3 ) - val processSati = ProcessSati(satiRepo) + val processSati = ProcessSati(enableSati, satiRepo) val entityToSave = SatiMapper.toEntity(sati) + whenever(enableSati.execute()).thenReturn(true) whenever(satiRepo.findByActionId(actionId)).thenReturn(existingInDb) whenever(satiRepo.save(entityToSave)).thenReturn(entityToSave) diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt index f1111b24c..f479d3c8a 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt @@ -268,8 +268,6 @@ class SatiModelMapperTest { assertThat(model.id).isEqualTo(entity.id) assertThat(model.actionId).isEqualTo(entity.actionId) assertThat(model.module).isEqualTo("M1") - assertThat(model.createdAt).isEqualTo(entity.createdAt) - assertThat(model.updatedAt).isEqualTo(entity.updatedAt) } @Test From 73bfa0448a22063ceaaa72deb3d1636be5c0cdf3 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 26 Jun 2026 15:40:23 +0200 Subject: [PATCH 4/4] fix(backend): test on save --- .../entities/mission/sati/SatiEntity.kt | 1 - .../entities/mission/sati/SatiEntityMapper.kt | 10 ++------- .../entities/mission/sati/SatiVesselEntity.kt | 2 -- .../api/bff/model/sati/SatiMapper.kt | 6 ++---- .../model/mission/sati/AddressModel.kt | 21 ++++++++++++++++++- .../model/mission/sati/ContactModel.kt | 21 ++++++++++++++++++- .../model/mission/sati/SatiInspectorModel.kt | 18 +++++++++++++++- .../database/model/mission/sati/SatiModel.kt | 7 ++++++- .../model/mission/sati/SatiModelMapper.kt | 17 ++++++++------- .../model/mission/sati/SatiPartyModel.kt | 18 +++++++++++++++- .../model/mission/sati/SatiVesselModel.kt | 19 ++++++++++++++++- .../mission/sati/SatiEntityMapperTest.kt | 2 +- .../api/model/sati/SatiMapperTest.kt | 2 -- .../api/model/sati/SatiModelMapperTest.kt | 12 +++++------ 14 files changed, 118 insertions(+), 38 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt index 31d3c54d2..5fcc8dcce 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntity.kt @@ -9,7 +9,6 @@ data class SatiEntity( var id: UUID? = null, var module: SatiModuleType, var actionId: String, - var jpe: SatiJpeEntity? = null, var resource: ControlResourceEntity? = null, var vessel: SatiVesselEntity? = null, var createdAt: Instant? = null, diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt index ec0071b36..503d6220a 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiEntityMapper.kt @@ -1,14 +1,8 @@ package fr.gouv.dgampa.rapportnav.domain.entities.mission.sati -import com.neovisionaries.i18n.CountryCode -import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity -import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlUnitResourceType -import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.LogbookMessagePurpose import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionAction import fr.gouv.dgampa.rapportnav.infrastructure.api.bff.model.v2.sati.Sati import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.SatiModelMapper.toModel -import java.time.Instant -import java.util.UUID object SatiEntityMapper { fun merge(sati: SatiEntity, action: MissionAction): SatiEntity { @@ -24,8 +18,8 @@ object SatiEntityMapper { portName = action.lastDeparturePortName, portId = action.lastDeparturePortLocode, lastStopDate = action.lastDepartureDateTime, - tripNumber = action.tripNumber ?: sati.vessel?.tripNumber, - pnoType = action.pnoPurpose ?: sati.vessel?.pnoType?.let { LogbookMessagePurpose.valueOf(it) }, + tripNumber = action.tripNumber ?: sati.vessel?.jpe?.tripNumber, + pnoType = action.pnoPurpose ?: sati.vessel?.jpe?.pnoType, ), ircs = action.ircs, imo = action.imo, diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt index 5950c4f3e..a910424d5 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/domain/entities/mission/sati/SatiVesselEntity.kt @@ -16,8 +16,6 @@ data class SatiVesselEntity( val owner: SatiPartyEntity? = null, val flagState: CountryCode? = null, val charterer: SatiPartyEntity? = null, - val pnoType: String? = null, - val tripNumber: String? = null, val agent: SatiPartyEntity? = null, val master: SatiPartyEntity? = null, val isMasterOwner: Boolean = false, diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt index 867a81a92..fb857cd59 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/api/bff/model/sati/SatiMapper.kt @@ -110,8 +110,6 @@ object SatiMapper { private fun SatiVessel.toEntity(): SatiVesselEntity { return SatiVesselEntity( id = id, - pnoType = pnoType, - tripNumber = tripNumber, isMasterOwner = isMasterOwner, jpe = jpe?.toEntity(), ircs = ircs, @@ -132,8 +130,8 @@ object SatiMapper { private fun SatiVesselEntity.toOutput(): SatiVessel { return SatiVessel( id = id, - pnoType = pnoType, - tripNumber = tripNumber, + pnoType = jpe?.pnoType?.name, + tripNumber = jpe?.tripNumber, isMasterOwner = isMasterOwner, jpe = jpe?.toOutput(), ircs = ircs, diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt index 5b0b951b0..1c6cdb569 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/AddressModel.kt @@ -11,6 +11,7 @@ import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant +import java.util.Objects @Entity @Table(name = "address") @@ -50,4 +51,22 @@ import java.time.Instant @LastModifiedDate @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null -) +) { + override fun hashCode(): Int { + return Objects.hash(id, street, fullAddress, zipcode, town, country, lng, lat) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as AddressModel + return id == other.id + && street == other.street + && fullAddress == other.fullAddress + && zipcode == other.zipcode + && town == other.town + && country == other.country + && lng == other.lng + && lat == other.lat + } +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt index 1a6309208..e50430fe2 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/ContactModel.kt @@ -15,6 +15,7 @@ import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant +import java.util.Objects @Entity @Table(name = "contact") @@ -55,4 +56,22 @@ class ContactModel( @LastModifiedDate @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null -) +) { + override fun hashCode(): Int { + return Objects.hash(id, fullName, firstName, lastName, nationality, email, phone, address) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as ContactModel + return id == other.id + && fullName == other.fullName + && firstName == other.firstName + && lastName == other.lastName + && nationality == other.nationality + && email == other.email + && phone == other.phone + && address == other.address + } +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt index 0e015affe..cbe77ab2f 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiInspectorModel.kt @@ -15,6 +15,7 @@ import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant +import java.util.Objects @Entity @Table(name = "sati_inspector") @@ -46,4 +47,19 @@ class SatiInspectorModel( @LastModifiedDate @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null, -) +) { + override fun hashCode(): Int { + return Objects.hash(id, party, authorityType, agentId, isOutOfUnit) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as SatiInspectorModel + return id == other.id + && party == other.party + && authorityType == other.authorityType + && agentId == other.agentId + && isOutOfUnit == other.isOutOfUnit + } +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt index a426097a4..4a203608e 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModel.kt @@ -63,7 +63,7 @@ class SatiModel( var updatedAt: Instant? = null ) { override fun hashCode(): Int { - return Objects.hash(id) + return Objects.hash(id, module, actionId, resourceId, vessel, inspectors) } override fun equals(other: Any?): Boolean { @@ -71,5 +71,10 @@ class SatiModel( if (javaClass != other?.javaClass) return false other as SatiModel return id == other.id + && module == other.module + && actionId == other.actionId + && resourceId == other.resourceId + && vessel == other.vessel + && inspectors == other.inspectors } } diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt index c962c6967..93120fef7 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiModelMapper.kt @@ -2,8 +2,9 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati import com.neovisionaries.i18n.CountryCode import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.LogbookMessagePurpose import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* -import java.util.UUID +import java.util.* object SatiModelMapper { fun toEntity(model: SatiModel): SatiEntity { @@ -115,10 +116,12 @@ object SatiModelMapper { private fun SatiVesselModel.toEntity(): SatiVesselEntity { return SatiVesselEntity( id = id, - pnoType = pnoType, + jpe = SatiJpeEntity( + tripNumber = tripNumber, + pnoType = pnoType?.let { LogbookMessagePurpose.valueOf(it) } + ), createdAt = createdAt, updatedAt = updatedAt, - tripNumber = tripNumber, agent = agent?.toEntity(), master = master?.toEntity(), isMasterOwner = isMasterOwner @@ -128,13 +131,11 @@ object SatiModelMapper { private fun SatiVesselEntity.toModel(): SatiVesselModel { return SatiVesselModel( id = id, - pnoType = pnoType, - createdAt = createdAt, - updatedAt = updatedAt, - tripNumber = tripNumber, agent = agent?.toModel(), master = master?.toModel(), - isMasterOwner = isMasterOwner + tripNumber = jpe?.tripNumber, + isMasterOwner = isMasterOwner, + pnoType = jpe?.pnoType?.toString() ) } diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt index 9e86c2eee..ba4cccfb1 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiPartyModel.kt @@ -15,6 +15,7 @@ import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant +import java.util.Objects @Entity @Table(name = "sati_party") @@ -46,4 +47,19 @@ class SatiPartyModel( @LastModifiedDate @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null -) +) { + override fun hashCode(): Int { + return Objects.hash(id, partyType, comments, signature, contact) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as SatiPartyModel + return id == other.id + && partyType == other.partyType + && comments == other.comments + && signature == other.signature + && contact == other.contact + } +} diff --git a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt index 9d891fc6a..def7d3f1e 100644 --- a/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt +++ b/backend/src/main/kotlin/fr/gouv/dgampa/rapportnav/infrastructure/database/model/mission/sati/SatiVesselModel.kt @@ -5,6 +5,7 @@ import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant +import java.util.* @Entity @Table(name = "sati_vessel") @@ -40,4 +41,20 @@ class SatiVesselModel( @LastModifiedDate @Column(name = "updated_at", nullable = true) var updatedAt: Instant? = null -) +) { + override fun hashCode(): Int { + return Objects.hash(id, pnoType, tripNumber, agent, master, isMasterOwner) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as SatiVesselModel + return id == other.id + && pnoType == other.pnoType + && tripNumber == other.tripNumber + && agent == other.agent + && master == other.master + && isMasterOwner == other.isMasterOwner + } +} diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt index b30134c97..fb2da2bd4 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/domain/entities/mission/sati/SatiEntityMapperTest.kt @@ -18,7 +18,7 @@ class SatiEntityMapperTest { id = 10, agent = SatiPartyEntity(id = 5, partyType = "AGENT"), master = SatiPartyEntity(id = 6, partyType = "MASTER"), - tripNumber = "EXISTING-TRIP" + jpe = SatiJpeEntity(tripNumber = "EXISTING-TRIP") ), inspectors: List = listOf( SatiInspectorEntity(id = 7, agentId = 42, authorityType = AuthorityType.AECP) diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt index abe289ca8..6cdea460f 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiMapperTest.kt @@ -66,8 +66,6 @@ class SatiMapperTest { owner = party, flagState = CountryCode.FR, charterer = SatiPartyEntity(id = 4, partyType = "CHARTERER"), - pnoType = "LAN", - tripNumber = "TRIP-001", agent = SatiPartyEntity(id = 5, partyType = "AGENT"), master = SatiPartyEntity(id = 6, partyType = "MASTER"), isMasterOwner = true diff --git a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt index f479d3c8a..9be325df4 100644 --- a/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt +++ b/backend/src/test/kotlin/fr/gouv/gmampa/rapportnav/infrastructure/api/model/sati/SatiModelMapperTest.kt @@ -2,6 +2,7 @@ package fr.gouv.dgampa.rapportnav.infrastructure.database.model.sati import com.neovisionaries.i18n.CountryCode import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.controlResources.ControlResourceEntity +import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.LogbookMessagePurpose import fr.gouv.dgampa.rapportnav.domain.entities.mission.sati.* import fr.gouv.dgampa.rapportnav.infrastructure.database.model.mission.sati.* import org.assertj.core.api.Assertions.assertThat @@ -142,8 +143,7 @@ class SatiModelMapperTest { val masterParty = SatiPartyEntity(id = 6, partyType = "MASTER", createdAt = timestamp, updatedAt = timestamp) val vessel = SatiVesselEntity( id = 10, - pnoType = "LAN", - tripNumber = "TRIP-001", + jpe = SatiJpeEntity(pnoType = LogbookMessagePurpose.LAN, tripNumber = "TRIP-001"), agent = agentParty, master = masterParty, isMasterOwner = true, @@ -200,8 +200,8 @@ class SatiModelMapperTest { val entity = SatiModelMapper.toEntity(model) assertThat(entity.vessel).isNotNull - assertThat(entity.vessel?.pnoType).isEqualTo("LAN") - assertThat(entity.vessel?.tripNumber).isEqualTo("TRIP-001") + assertThat(entity.vessel?.jpe?.pnoType).isEqualTo(LogbookMessagePurpose.LAN) + assertThat(entity.vessel?.jpe?.tripNumber).isEqualTo("TRIP-001") assertThat(entity.vessel?.isMasterOwner).isTrue() } @@ -363,8 +363,8 @@ class SatiModelMapperTest { assertThat(roundTripped.id).isEqualTo(entity.id) assertThat(roundTripped.actionId).isEqualTo(entity.actionId) assertThat(roundTripped.module).isEqualTo(entity.module) - assertThat(roundTripped.vessel?.pnoType).isEqualTo(entity.vessel?.pnoType) - assertThat(roundTripped.vessel?.tripNumber).isEqualTo(entity.vessel?.tripNumber) + assertThat(roundTripped.vessel?.jpe?.pnoType).isEqualTo(entity.vessel?.jpe?.pnoType) + assertThat(roundTripped.vessel?.jpe?.tripNumber).isEqualTo(entity.vessel?.jpe?.tripNumber) assertThat(roundTripped.vessel?.isMasterOwner).isEqualTo(entity.vessel?.isMasterOwner) } }