From f8d58b8b65af572084ebba05d385d6551fccc40d Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Tue, 10 Jun 2025 17:02:15 +0200 Subject: [PATCH] [Reportings] save only sub-themes in database --- .../database/model/ThemeReportingModel.kt | 68 ++++++---- .../reportings/AbstractReportingModel.kt | 26 +--- .../repositories/JpaReportingRepository.kt | 4 +- .../JpaReportingRepositoryITests.kt | 117 ++++++++++++++++-- 4 files changed, 156 insertions(+), 59 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ThemeReportingModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ThemeReportingModel.kt index 142b471c9f..00e2808f56 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ThemeReportingModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ThemeReportingModel.kt @@ -3,14 +3,7 @@ package fr.gouv.cacem.monitorenv.infrastructure.database.model import com.fasterxml.jackson.annotation.JsonBackReference import fr.gouv.cacem.monitorenv.domain.entities.themes.ThemeEntity import fr.gouv.cacem.monitorenv.infrastructure.database.model.reportings.ReportingModel -import jakarta.persistence.Embeddable -import jakarta.persistence.EmbeddedId -import jakarta.persistence.Entity -import jakarta.persistence.FetchType -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.MapsId -import jakarta.persistence.Table +import jakarta.persistence.* import java.io.Serializable @Entity @@ -30,34 +23,57 @@ data class ThemeReportingModel( ) { companion object { fun toThemeEntities(themes: List): List { - val parents = themes.map { it.theme }.filter { it.parent === null } + val parentsFromSubThemes = + themes + .map { it.theme } + .filter { it.parent !== null } + .distinctBy { it.parent?.id } + .map { it.parent } - return parents.map { parent -> - val subThemes = themes.filter { it.theme.parent?.id == parent.id }.map { it.theme } - parent.subThemes = subThemes - return@map parent.toThemeEntity() + if (parentsFromSubThemes.isNotEmpty()) { + return parentsFromSubThemes.map { parent -> + if (parent == null) { + return listOf() + } + val subThemes = themes.filter { it.theme.parent?.id == parent.id }.map { it.theme } + parent.subThemes = subThemes + return@map parent.toThemeEntity() + } } + + val themesWithoutSubThemes = themes.map { it.theme }.filter { it.parent === null } + if (themesWithoutSubThemes.isNotEmpty()) { + return themesWithoutSubThemes.map { theme -> + theme.subThemes = emptyList() + theme.toThemeEntity() + } + } + + return emptyList() } fun fromThemeEntity( theme: ThemeEntity, reporting: ReportingModel, ): MutableSet = - listOf( - ThemeReportingModel( - id = ThemeReportingPk(theme.id, reporting.id), - theme = ThemeModel.fromThemeEntity(theme), - reporting = reporting, - ), - ).plus( - theme.subThemes.map { subTheme -> + if (theme.subThemes.isEmpty()) { + mutableSetOf( ThemeReportingModel( - id = ThemeReportingPk(subTheme.id, reporting.id), - theme = ThemeModel.fromThemeEntity(subTheme), + id = ThemeReportingPk(theme.id, reporting.id), + theme = ThemeModel.fromThemeEntity(theme), reporting = reporting, - ) - }, - ).toMutableSet() + ), + ) + } else { + theme.subThemes + .map { subTheme -> + ThemeReportingModel( + id = ThemeReportingPk(subTheme.id, reporting.id), + theme = ThemeModel.fromThemeEntity(subTheme), + reporting = reporting, + ) + }.toMutableSet() + } } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/reportings/AbstractReportingModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/reportings/AbstractReportingModel.kt index 2cf2f6ba95..6a1afdbe40 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/reportings/AbstractReportingModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/reportings/AbstractReportingModel.kt @@ -12,34 +12,15 @@ import fr.gouv.cacem.monitorenv.domain.entities.reporting.TargetDetailsEntity import fr.gouv.cacem.monitorenv.domain.entities.reporting.TargetTypeEnum import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingDetailsDTO import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingListDTO -import fr.gouv.cacem.monitorenv.infrastructure.database.model.EnvActionModel -import fr.gouv.cacem.monitorenv.infrastructure.database.model.MissionModel -import fr.gouv.cacem.monitorenv.infrastructure.database.model.ReportingSourceModel -import fr.gouv.cacem.monitorenv.infrastructure.database.model.TagReportingModel +import fr.gouv.cacem.monitorenv.infrastructure.database.model.* import fr.gouv.cacem.monitorenv.infrastructure.database.model.TagReportingModel.Companion.toTagEntities -import fr.gouv.cacem.monitorenv.infrastructure.database.model.ThemeReportingModel import fr.gouv.cacem.monitorenv.infrastructure.database.model.ThemeReportingModel.Companion.toThemeEntities import io.hypersistence.utils.hibernate.type.json.JsonBinaryType +import jakarta.persistence.* import jakarta.persistence.CascadeType -import jakarta.persistence.Column -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated -import jakarta.persistence.FetchType -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.ManyToOne -import jakarta.persistence.MappedSuperclass -import jakarta.persistence.OneToMany import jakarta.persistence.OrderBy import org.hibernate.Hibernate -import org.hibernate.annotations.Fetch -import org.hibernate.annotations.FetchMode -import org.hibernate.annotations.Generated -import org.hibernate.annotations.JdbcType -import org.hibernate.annotations.Type -import org.hibernate.annotations.UpdateTimestamp +import org.hibernate.annotations.* import org.hibernate.dialect.PostgreSQLEnumJdbcType import org.hibernate.generator.EventType import org.hibernate.type.descriptor.jdbc.UUIDJdbcType @@ -190,6 +171,7 @@ abstract class AbstractReportingModel( fun toReportingDetailsDTO(objectMapper: ObjectMapper): ReportingDetailsDTO { val reporting = this.toReporting() + println("Reporting details: $reporting") return ReportingDetailsDTO( reporting = reporting, reportingSources = reportingSources.map { it.toReportingSourceDTO() }, diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt index 01aef16732..2bcf4ff9a6 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepository.kt @@ -31,8 +31,6 @@ import java.util.* class JpaReportingRepository( private val dbReportingRepository: IDBReportingRepository, private val dbMissionRepository: IDBMissionRepository, - private val dbControlPlanThemeRepository: IDBControlPlanThemeRepository, - private val dbControlPlanSubThemeRepository: IDBControlPlanSubThemeRepository, private val dbEnvActionRepository: IDBEnvActionRepository, private val dbControlUnitRepository: IDBControlUnitRepository, private val dbSemaphoreRepository: IDBSemaphoreRepository, @@ -161,7 +159,7 @@ class JpaReportingRepository( null } - // To save controlPlanSubThemes we must ensure that reportingId is set + // To save thmes and tags we must ensure that reportingId is set // to simplify the understandability of the code, we do the same steps for creation and // update // even if it is not necessary for update diff --git a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepositoryITests.kt index 98dd1aab5d..c792466f0d 100644 --- a/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/repositories/JpaReportingRepositoryITests.kt @@ -406,17 +406,23 @@ class JpaReportingRepositoryITests : AbstractDBTests() { ) val theme = aTheme( - id = 2, - name = "AMP sans réglementation particulière", + id = 105, + name = "Épave", startedAt = ZonedDateTime.parse("2023-01-01T00:00Z"), endedAt = ZonedDateTime.parse("2099-12-31T23:59:59Z"), subThemes = listOf( aTheme( - id = 160, - name = "Contrôle dans une AMP sans réglementation particulière", - startedAt = ZonedDateTime.parse("2023-01-01T00:00Z"), - endedAt = ZonedDateTime.parse("2023-12-31T00:00Z"), + id = 228, + name = "Découverte d'une épave maritime", + startedAt = ZonedDateTime.parse("2024-01-01T00:00Z"), + endedAt = ZonedDateTime.parse("2024-12-31T00:00Z"), + ), + aTheme( + id = 241, + name = "Épave / Navire abandonné", + startedAt = ZonedDateTime.parse("2024-01-01T00:00Z"), + endedAt = ZonedDateTime.parse("2024-12-31T00:00Z"), ), ), ) @@ -485,13 +491,108 @@ class JpaReportingRepositoryITests : AbstractDBTests() { assertThat(reportingDTO.reporting.theme.name).isEqualTo(theme.name) assertThat(reportingDTO.reporting.theme.subThemes).hasSize(theme.subThemes.size) reportingDTO.reporting.theme.subThemes.find { it.id == theme.subThemes[0].id }.let { - assertThat(it?.id).isEqualTo(160) - assertThat(it?.name).isEqualTo("Contrôle dans une AMP sans réglementation particulière") + assertThat(it?.id).isEqualTo(228) + assertThat(it?.name).isEqualTo("Découverte d'une épave maritime") + } + reportingDTO.reporting.theme.subThemes.find { it.id == theme.subThemes[1].id }.let { + assertThat(it?.id).isEqualTo(241) + assertThat(it?.name).isEqualTo("Épave / Navire abandonné") } val numberOfExistingReportingsAfterSave = jpaReportingRepository.count() assertThat(numberOfExistingReportingsAfterSave).isEqualTo(12) } + @Test + @Transactional + fun `save should create a new Reporting with no sub-themes`() { + // Given + val numberOfExistingReportings = jpaReportingRepository.count() + assertThat(numberOfExistingReportings).isEqualTo(11) + + // When + val wktReader = WKTReader() + val multipolygonString = + "MULTIPOLYGON (((-4.54877816747593 48.305559876971, -4.54997332394943 48.3059760121399, -4.54998501370013 48.3071882334181, -4.54879290083417 48.3067746138142, -4.54877816747593 48.305559876971)))" + val polygon = wktReader.read(multipolygonString) as MultiPolygon + + // Given + val theme = + aTheme( + id = 105, + name = "Épave", + startedAt = ZonedDateTime.parse("2023-01-01T00:00Z"), + endedAt = ZonedDateTime.parse("2099-12-31T23:59:59Z"), + subThemes = listOf(), + ) + + val newReporting = + ReportingEntity( + reportingSources = + listOf( + ReportingSourceEntity( + id = null, + sourceType = SourceTypeEnum.SEMAPHORE, + semaphoreId = 21, + controlUnitId = null, + sourceName = null, + reportingId = null, + ), + ), + targetType = TargetTypeEnum.VEHICLE, + vehicleType = VehicleTypeEnum.VESSEL, + geom = polygon, + seaFront = "NAMO", + description = "Test reporting", + reportType = ReportingTypeEnum.INFRACTION_SUSPICION, + actionTaken = "Aucune", + isControlRequired = false, + hasNoUnitAvailable = false, + createdAt = ZonedDateTime.parse("2023-04-01T00:00:00Z"), + validityTime = 24, + isArchived = false, + isDeleted = false, + openBy = "CDA", + isInfractionProven = true, + tags = listOf(), + theme = theme, + ) + + jpaReportingRepository.save(newReporting) + + val reportingDTO = jpaReportingRepository.findById(12) + + // Then + assertThat(reportingDTO.reporting.id).isEqualTo(12) + + val currentYear = Year.now().value.toString() + val reportingId = (currentYear.substring(currentYear.length - 2) + "00001").toLong() + assertThat(reportingDTO.reporting.reportingId).isEqualTo(reportingId) + assertThat(reportingDTO.reporting.reportingSources[0].sourceType).isEqualTo(SourceTypeEnum.SEMAPHORE) + assertThat(reportingDTO.reporting.reportingSources[0].semaphoreId).isEqualTo(21) + assertThat(reportingDTO.reporting.targetType).isEqualTo(TargetTypeEnum.VEHICLE) + assertThat(reportingDTO.reporting.vehicleType).isEqualTo(VehicleTypeEnum.VESSEL) + assertThat(reportingDTO.reporting.geom).isEqualTo(polygon) + assertThat(reportingDTO.reporting.seaFront).isEqualTo("NAMO") + assertThat(reportingDTO.reporting.description).isEqualTo("Test reporting") + assertThat(reportingDTO.reporting.reportType) + .isEqualTo(ReportingTypeEnum.INFRACTION_SUSPICION) + assertThat(reportingDTO.reporting.actionTaken).isEqualTo("Aucune") + assertThat(reportingDTO.reporting.isControlRequired).isEqualTo(false) + assertThat(reportingDTO.reporting.hasNoUnitAvailable).isEqualTo(false) + assertThat(reportingDTO.reporting.createdAt) + .isEqualTo(ZonedDateTime.parse("2023-04-01T00:00:00Z")) + assertThat(reportingDTO.reporting.validityTime).isEqualTo(24) + assertThat(reportingDTO.reporting.isArchived).isEqualTo(false) + assertThat(reportingDTO.reporting.openBy).isEqualTo("CDA") + assertThat(reportingDTO.reporting.updatedAtUtc).isAfter(ZonedDateTime.now().minusMinutes(1)) + assertThat(reportingDTO.reporting.theme.id).isEqualTo(theme.id) + assertThat(reportingDTO.reporting.theme.name).isEqualTo(theme.name) + assertThat(reportingDTO.reporting.theme.subThemes).hasSize(0) + + val numberOfExistingReportingsAfterSave = jpaReportingRepository.count() + assertThat(numberOfExistingReportingsAfterSave).isEqualTo(12) + } + @Test @Transactional fun `save should update an existing Reporting`() {