From 0fdfa031f5a8769b52f5f67aec34e69f0c2d9026 Mon Sep 17 00:00:00 2001 From: Claire Dagan Date: Fri, 13 Oct 2023 15:37:51 +0200 Subject: [PATCH] [Reporting] clean cache for reporting and mission apis, add attached and detached mission date in back --- .../reportings/CreateOrUpdateReporting.kt | 35 +- .../bff/outputs/ReportingDataOutput.kt | 184 ++++++----- .../database/model/ReportingModel.kt | 311 +++++++++--------- .../repositories/JpaReportingRepository.kt | 102 +++--- frontend/src/api/missionsAPI.ts | 12 +- frontend/src/api/reportingsAPI.ts | 11 +- frontend/src/domain/entities/missions.ts | 4 +- frontend/src/domain/entities/reporting.ts | 13 + .../use_cases/reporting/attachMission.ts | 23 ++ .../Layers/MissionToAttach/index.tsx | 4 +- .../AttachMission/AttachedMissionCard.tsx | 10 +- .../ReportingForm/AttachMission/index.tsx | 14 +- .../ReportingsList/Cells/CellActionStatus.tsx | 8 +- .../ReportingsList/Columns/index.tsx | 2 +- .../Reportings/components/StatusActionTag.tsx | 10 +- .../Reportings/reportingsGeometryHelpers.ts | 2 + .../map/overlays/reportings/ReportingCard.tsx | 28 +- 17 files changed, 427 insertions(+), 346 deletions(-) create mode 100644 frontend/src/domain/use_cases/reporting/attachMission.ts diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt index cf6d56cb2..eaffd678c 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/reportings/CreateOrUpdateReporting.kt @@ -4,24 +4,23 @@ import fr.gouv.cacem.monitorenv.config.UseCase import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingEntity import fr.gouv.cacem.monitorenv.domain.repositories.* import fr.gouv.cacem.monitorenv.domain.use_cases.reportings.dtos.ReportingDTO +import java.time.ZonedDateTime import org.slf4j.Logger import org.slf4j.LoggerFactory @UseCase class CreateOrUpdateReporting( - private val reportingRepository: IReportingRepository, - private val controlUnitRepository: IControlUnitRepository, - private val semaphoreRepository: ISemaphoreRepository, - private val facadeRepository: IFacadeAreasRepository, - private val missionRepository: IMissionRepository, + private val reportingRepository: IReportingRepository, + private val controlUnitRepository: IControlUnitRepository, + private val semaphoreRepository: ISemaphoreRepository, + private val facadeRepository: IFacadeAreasRepository, + private val missionRepository: IMissionRepository, ) { private val logger: Logger = LoggerFactory.getLogger(CreateOrUpdateReporting::class.java) @Throws(IllegalArgumentException::class) fun execute(reporting: ReportingEntity?): ReportingDTO { - require(reporting != null) { - "No reporting to create or update" - } + require(reporting != null) { "No reporting to create or update" } logger.info("Create or update reporting: $reporting.id") reporting.checkValidity() @@ -30,7 +29,25 @@ class CreateOrUpdateReporting( seaFront = facadeRepository.findFacadeFromGeometry(reporting.geom) } - val savedReport = reportingRepository.save(reporting.copy(seaFront = seaFront)) + var attachedToMissionAtUtc: ZonedDateTime? = null + var detachedFromMissionAtUtc: ZonedDateTime? = null + if (reporting.missionId != null) { + attachedToMissionAtUtc = ZonedDateTime.now() + detachedFromMissionAtUtc = null + } + + if (reporting.missionId == null && reporting.attachedToMissionAtUtc != null) { + detachedFromMissionAtUtc = ZonedDateTime.now() + } + + val savedReport = + reportingRepository.save( + reporting.copy( + seaFront = seaFront, + attachedToMissionAtUtc = attachedToMissionAtUtc, + detachedFromMissionAtUtc = detachedFromMissionAtUtc + ) + ) return savedReport } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt index 933892dc0..6840bfc85 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/ReportingDataOutput.kt @@ -1,111 +1,117 @@ package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs import fr.gouv.cacem.monitorenv.domain.entities.VehicleTypeEnum +import fr.gouv.cacem.monitorenv.domain.entities.reporting.ControlStatusEnum import fr.gouv.cacem.monitorenv.domain.entities.reporting.ReportingTypeEnum import fr.gouv.cacem.monitorenv.domain.entities.reporting.SourceTypeEnum 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.ReportingDTO import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.outputs.ControlUnitDataOutput -import org.locationtech.jts.geom.Geometry import java.time.ZonedDateTime import java.util.UUID +import org.locationtech.jts.geom.Geometry data class ReportingDataOutput( - val id: Int, - val reportingId: Long? = null, - val sourceType: SourceTypeEnum? = null, - val semaphoreId: Int? = null, - val semaphore: SemaphoreDataOutput? = null, - val controlUnitId: Int? = null, - val controlUnit: ControlUnitDataOutput? = null, - val displayedSource: String? = null, - val sourceName: String? = null, - val targetType: TargetTypeEnum? = null, - val vehicleType: VehicleTypeEnum? = null, - val targetDetails: List? = listOf(), - val geom: Geometry? = null, - val seaFront: String? = null, - val description: String? = null, - val reportType: ReportingTypeEnum? = null, - val theme: String? = null, - val subThemes: List? = listOf(), - val actionTaken: String? = null, - val isControlRequired: Boolean? = null, - val hasNoUnitAvailable: Boolean? = null, - val createdAt: ZonedDateTime, - val validityTime: Int? = null, - val isArchived: Boolean, - val openBy: String? = null, - val missionId: Int? = null, - val attachedToMissionAtUtc: ZonedDateTime? = null, - val detachedFromMissionAtUtc: ZonedDateTime? = null, - val attachedEnvActionId: UUID? = null, - val attachedMission: ReportingMissionDataOutput? = null, + val id: Int, + val reportingId: Long? = null, + val sourceType: SourceTypeEnum? = null, + val semaphoreId: Int? = null, + val semaphore: SemaphoreDataOutput? = null, + val controlUnitId: Int? = null, + val controlUnit: ControlUnitDataOutput? = null, + val displayedSource: String? = null, + val sourceName: String? = null, + val targetType: TargetTypeEnum? = null, + val vehicleType: VehicleTypeEnum? = null, + val targetDetails: List? = listOf(), + val geom: Geometry? = null, + val seaFront: String? = null, + val description: String? = null, + val reportType: ReportingTypeEnum? = null, + val theme: String? = null, + val subThemes: List? = listOf(), + val actionTaken: String? = null, + val isControlRequired: Boolean? = null, + val hasNoUnitAvailable: Boolean? = null, + val createdAt: ZonedDateTime, + val validityTime: Int? = null, + val isArchived: Boolean, + val openBy: String? = null, + val missionId: Int? = null, + val attachedToMissionAtUtc: ZonedDateTime? = null, + val detachedFromMissionAtUtc: ZonedDateTime? = null, + val attachedEnvActionId: UUID? = null, + val attachedMission: ReportingMissionDataOutput? = null, + val controlStatus: ControlStatusEnum? = null, ) { companion object { fun fromReportingDTO( - dto: ReportingDTO, + dto: ReportingDTO, ): ReportingDataOutput { requireNotNull(dto.reporting.id) { "ReportingEntity.id cannot be null" } return ReportingDataOutput( - id = dto.reporting.id!!, - reportingId = dto.reporting.reportingId, - sourceType = dto.reporting.sourceType, - semaphoreId = dto.reporting.semaphoreId, - semaphore = if (dto.semaphore != null) { - SemaphoreDataOutput.fromSemaphoreEntity( - dto.semaphore, - ) - } else { - null - }, - controlUnitId = dto.reporting.controlUnitId, - controlUnit = - if (dto.controlUnit != null) { - ControlUnitDataOutput - .fromFullControlUnit( - dto.controlUnit, - ) - } else { - null - }, - displayedSource = - when (dto.reporting.sourceType) { - SourceTypeEnum.SEMAPHORE -> dto?.semaphore?.unit ?: dto?.semaphore?.name - // TODO This is really strange : `fullControlUnit?.controlUnit` can't be null and I have to add another `?`... - SourceTypeEnum.CONTROL_UNIT -> dto?.controlUnit?.controlUnit?.name - SourceTypeEnum.OTHER -> dto.reporting.sourceName - else -> "" - }, - sourceName = dto.reporting.sourceName, - targetType = dto.reporting.targetType, - vehicleType = dto.reporting.vehicleType, - targetDetails = dto.reporting.targetDetails, - geom = dto.reporting.geom, - seaFront = dto.reporting.seaFront, - description = dto.reporting.description, - reportType = dto.reporting.reportType, - theme = dto.reporting.theme, - subThemes = dto.reporting.subThemes, - actionTaken = dto.reporting.actionTaken, - isControlRequired = dto.reporting.isControlRequired, - hasNoUnitAvailable = dto.reporting.hasNoUnitAvailable, - createdAt = dto.reporting.createdAt, - validityTime = dto.reporting.validityTime, - isArchived = dto.reporting.isArchived, - openBy = dto.reporting.openBy, - missionId = dto.reporting.missionId, - attachedToMissionAtUtc = dto.reporting.attachedToMissionAtUtc, - detachedFromMissionAtUtc = dto.reporting.detachedFromMissionAtUtc, - attachedEnvActionId = dto.reporting.attachedEnvActionId, - attachedMission = if (dto.attachedMission != null) { - ReportingMissionDataOutput.fromMission( - dto.attachedMission, - ) - } else { - null - }, + id = dto.reporting.id!!, + reportingId = dto.reporting.reportingId, + sourceType = dto.reporting.sourceType, + semaphoreId = dto.reporting.semaphoreId, + semaphore = + if (dto.semaphore != null) { + SemaphoreDataOutput.fromSemaphoreEntity( + dto.semaphore, + ) + } else { + null + }, + controlUnitId = dto.reporting.controlUnitId, + controlUnit = + if (dto.controlUnit != null) { + ControlUnitDataOutput.fromFullControlUnit( + dto.controlUnit, + ) + } else { + null + }, + displayedSource = + when (dto.reporting.sourceType) { + SourceTypeEnum.SEMAPHORE -> dto?.semaphore?.unit + ?: dto?.semaphore?.name + // TODO This is really strange : `fullControlUnit?.controlUnit` + // can't be null and I have to add another `?`... + SourceTypeEnum.CONTROL_UNIT -> dto?.controlUnit?.controlUnit?.name + SourceTypeEnum.OTHER -> dto.reporting.sourceName + else -> "" + }, + sourceName = dto.reporting.sourceName, + targetType = dto.reporting.targetType, + vehicleType = dto.reporting.vehicleType, + targetDetails = dto.reporting.targetDetails, + geom = dto.reporting.geom, + seaFront = dto.reporting.seaFront, + description = dto.reporting.description, + reportType = dto.reporting.reportType, + theme = dto.reporting.theme, + subThemes = dto.reporting.subThemes, + actionTaken = dto.reporting.actionTaken, + isControlRequired = dto.reporting.isControlRequired, + hasNoUnitAvailable = dto.reporting.hasNoUnitAvailable, + createdAt = dto.reporting.createdAt, + validityTime = dto.reporting.validityTime, + isArchived = dto.reporting.isArchived, + openBy = dto.reporting.openBy, + controlStatus = dto.controlStatus, + missionId = dto.reporting.missionId, + attachedToMissionAtUtc = dto.reporting.attachedToMissionAtUtc, + detachedFromMissionAtUtc = dto.reporting.detachedFromMissionAtUtc, + attachedEnvActionId = dto.reporting.attachedEnvActionId, + attachedMission = + if (dto.attachedMission != null) { + ReportingMissionDataOutput.fromMission( + dto.attachedMission, + ) + } else { + null + }, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt index 85d12a6dc..ff81c1372 100644 --- a/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt +++ b/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/database/model/ReportingModel.kt @@ -25,6 +25,9 @@ import jakarta.persistence.Id import jakarta.persistence.JoinColumn import jakarta.persistence.ManyToOne import jakarta.persistence.Table +import java.time.Instant +import java.time.ZoneOffset.UTC +import java.util.UUID import org.hibernate.Hibernate import org.hibernate.annotations.Generated import org.hibernate.annotations.GenerationTime @@ -34,136 +37,134 @@ import org.hibernate.type.descriptor.jdbc.UUIDJdbcType import org.locationtech.jts.geom.Geometry import org.n52.jackson.datatype.jts.GeometryDeserializer import org.n52.jackson.datatype.jts.GeometrySerializer -import java.time.Instant -import java.time.ZoneOffset.UTC -import java.util.UUID @Entity @Table(name = "reportings") data class ReportingModel( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", unique = true, nullable = false) - val id: Int? = null, - @Generated(GenerationTime.INSERT) - @Column( - name = "reporting_id", - unique = true, - nullable = false, - updatable = false, - insertable = false, - ) - val reportingId: Long? = null, - @Column(name = "source_type", columnDefinition = "reportings_source_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val sourceType: SourceTypeEnum? = null, - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "semaphore_id", nullable = true) - @JsonBackReference - val semaphore: SemaphoreModel? = null, - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "control_unit_id", nullable = true) - @JsonBackReference - val controlUnit: ControlUnitModel? = null, - @Column(name = "source_name") val sourceName: String? = null, - @Column(name = "target_type", columnDefinition = "reportings_target_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val targetType: TargetTypeEnum? = null, - @Column(name = "vehicle_type", columnDefinition = "reportings_vehicle_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val vehicleType: VehicleTypeEnum? = null, - @Column(name = "target_details", columnDefinition = "jsonb") - @Type(JsonBinaryType::class) - val targetDetails: List? = listOf(), - @JsonSerialize(using = GeometrySerializer::class) - @JsonDeserialize(contentUsing = GeometryDeserializer::class) - @Column(name = "geom") - val geom: Geometry? = null, - @Column(name = "sea_front") val seaFront: String? = null, - @Column(name = "description") val description: String? = null, - @Column(name = "report_type", columnDefinition = "reportings_report_type") - @Enumerated(EnumType.STRING) - @Type(PostgreSQLEnumType::class) - val reportType: ReportingTypeEnum? = null, - @Column(name = "theme") val theme: String? = null, - @Column(name = "sub_themes") - @Type(ListArrayType::class) - val subThemes: List? = listOf(), - @Column(name = "action_taken") val actionTaken: String? = null, - @Column(name = "is_control_required") val isControlRequired: Boolean? = null, - @Column(name = "has_no_unit_available") val hasNoUnitAvailable: Boolean? = null, - @Column(name = "created_at") val createdAt: Instant, - @Column(name = "validity_time") val validityTime: Int? = null, - @Column(name = "is_archived", nullable = false) val isArchived: Boolean, - @Column(name = "is_deleted", nullable = false) val isDeleted: Boolean, - @Column(name = "open_by") val openBy: String? = null, - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "mission_id", nullable = true) - @JsonBackReference - val mission: MissionModel? = null, - @Column(name = "attached_to_mission_at_utc") val attachedToMissionAtUtc: Instant? = null, - @Column(name = "detached_from_mission_at_utc") - val detachedFromMissionAtUtc: Instant? = null, - @JdbcType(UUIDJdbcType::class) - @Column(name = "attached_env_action_id", columnDefinition = "uuid") - val attachedEnvActionId: UUID? = null, + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", unique = true, nullable = false) + val id: Int? = null, + @Generated(GenerationTime.INSERT) + @Column( + name = "reporting_id", + unique = true, + nullable = false, + updatable = false, + insertable = false, + ) + val reportingId: Long? = null, + @Column(name = "source_type", columnDefinition = "reportings_source_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val sourceType: SourceTypeEnum? = null, + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "semaphore_id", nullable = true) + @JsonBackReference + val semaphore: SemaphoreModel? = null, + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "control_unit_id", nullable = true) + @JsonBackReference + val controlUnit: ControlUnitModel? = null, + @Column(name = "source_name") val sourceName: String? = null, + @Column(name = "target_type", columnDefinition = "reportings_target_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val targetType: TargetTypeEnum? = null, + @Column(name = "vehicle_type", columnDefinition = "reportings_vehicle_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val vehicleType: VehicleTypeEnum? = null, + @Column(name = "target_details", columnDefinition = "jsonb") + @Type(JsonBinaryType::class) + val targetDetails: List? = listOf(), + @JsonSerialize(using = GeometrySerializer::class) + @JsonDeserialize(contentUsing = GeometryDeserializer::class) + @Column(name = "geom") + val geom: Geometry? = null, + @Column(name = "sea_front") val seaFront: String? = null, + @Column(name = "description") val description: String? = null, + @Column(name = "report_type", columnDefinition = "reportings_report_type") + @Enumerated(EnumType.STRING) + @Type(PostgreSQLEnumType::class) + val reportType: ReportingTypeEnum? = null, + @Column(name = "theme") val theme: String? = null, + @Column(name = "sub_themes") + @Type(ListArrayType::class) + val subThemes: List? = listOf(), + @Column(name = "action_taken") val actionTaken: String? = null, + @Column(name = "is_control_required") val isControlRequired: Boolean? = null, + @Column(name = "has_no_unit_available") val hasNoUnitAvailable: Boolean? = null, + @Column(name = "created_at") val createdAt: Instant, + @Column(name = "validity_time") val validityTime: Int? = null, + @Column(name = "is_archived", nullable = false) val isArchived: Boolean, + @Column(name = "is_deleted", nullable = false) val isDeleted: Boolean, + @Column(name = "open_by") val openBy: String? = null, + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "mission_id", nullable = true) + @JsonBackReference + val mission: MissionModel? = null, + @Column(name = "attached_to_mission_at_utc") val attachedToMissionAtUtc: Instant? = null, + @Column(name = "detached_from_mission_at_utc") + val detachedFromMissionAtUtc: Instant? = null, + @JdbcType(UUIDJdbcType::class) + @Column(name = "attached_env_action_id", columnDefinition = "uuid") + val attachedEnvActionId: UUID? = null, ) { fun toReporting() = - ReportingEntity( - id = id, - reportingId = reportingId, - sourceType = sourceType, - semaphoreId = semaphore?.id, - controlUnitId = controlUnit?.id, - sourceName = sourceName, - targetType = targetType, - vehicleType = vehicleType, - targetDetails = targetDetails, - geom = geom, - seaFront = seaFront, - description = description, - reportType = reportType, - theme = theme, - subThemes = subThemes, - actionTaken = actionTaken, - isControlRequired = isControlRequired, - hasNoUnitAvailable = hasNoUnitAvailable, - createdAt = createdAt.atZone(UTC), - validityTime = validityTime, - isArchived = isArchived, - isDeleted = isDeleted, - openBy = openBy, - missionId = mission?.id, - attachedToMissionAtUtc = attachedToMissionAtUtc?.atZone(UTC), - detachedFromMissionAtUtc = detachedFromMissionAtUtc?.atZone(UTC), - attachedEnvActionId = attachedEnvActionId, - ) + ReportingEntity( + id = id, + reportingId = reportingId, + sourceType = sourceType, + semaphoreId = semaphore?.id, + controlUnitId = controlUnit?.id, + sourceName = sourceName, + targetType = targetType, + vehicleType = vehicleType, + targetDetails = targetDetails, + geom = geom, + seaFront = seaFront, + description = description, + reportType = reportType, + theme = theme, + subThemes = subThemes, + actionTaken = actionTaken, + isControlRequired = isControlRequired, + hasNoUnitAvailable = hasNoUnitAvailable, + createdAt = createdAt.atZone(UTC), + validityTime = validityTime, + isArchived = isArchived, + isDeleted = isDeleted, + openBy = openBy, + missionId = mission?.id, + attachedToMissionAtUtc = attachedToMissionAtUtc?.atZone(UTC), + detachedFromMissionAtUtc = detachedFromMissionAtUtc?.atZone(UTC), + attachedEnvActionId = attachedEnvActionId, + ) fun toReportingDTO(objectMapper: ObjectMapper) = - ReportingDTO( - reporting = this.toReporting(), - controlUnit = controlUnit?.toFullControlUnit(), - semaphore = semaphore?.toSemaphore(), - attachedMission = - if (detachedFromMissionAtUtc != null) { - mission?.toMissionEntity( - objectMapper, - ) - } else { - null - }, - detachedMission = - if (detachedFromMissionAtUtc == null) { - mission?.toMissionEntity( - objectMapper, - ) - } else { - null - }, - ) + ReportingDTO( + reporting = this.toReporting(), + controlUnit = controlUnit?.toFullControlUnit(), + semaphore = semaphore?.toSemaphore(), + attachedMission = + if (detachedFromMissionAtUtc == null && attachedToMissionAtUtc != null + ) { + mission?.toMissionEntity( + objectMapper, + ) + } else { + null + }, + detachedMission = + if (detachedFromMissionAtUtc != null) { + mission?.toMissionEntity( + objectMapper, + ) + } else { + null + }, + ) override fun equals(other: Any?): Boolean { if (this === other) return true @@ -177,39 +178,39 @@ data class ReportingModel( companion object { fun fromReportingEntity( - reporting: ReportingEntity, - semaphoreReference: SemaphoreModel?, - controlUnitReference: ControlUnitModel?, - missionReference: MissionModel?, + reporting: ReportingEntity, + semaphoreReference: SemaphoreModel?, + controlUnitReference: ControlUnitModel?, + missionReference: MissionModel?, ) = - ReportingModel( - id = reporting.id, - reportingId = reporting.reportingId, - sourceType = reporting.sourceType, - semaphore = semaphoreReference, - controlUnit = controlUnitReference, - sourceName = reporting.sourceName, - targetType = reporting.targetType, - vehicleType = reporting.vehicleType, - targetDetails = reporting.targetDetails, - geom = reporting.geom, - seaFront = reporting.seaFront, - description = reporting.description, - reportType = reporting.reportType, - theme = reporting.theme, - subThemes = reporting.subThemes, - actionTaken = reporting.actionTaken, - isControlRequired = reporting.isControlRequired, - hasNoUnitAvailable = reporting.hasNoUnitAvailable, - createdAt = reporting.createdAt.toInstant(), - validityTime = reporting.validityTime, - isArchived = reporting.isArchived, - isDeleted = reporting.isDeleted, - openBy = reporting.openBy, - mission = missionReference, - attachedToMissionAtUtc = reporting.attachedToMissionAtUtc?.toInstant(), - detachedFromMissionAtUtc = reporting.detachedFromMissionAtUtc?.toInstant(), - attachedEnvActionId = reporting.attachedEnvActionId, - ) + ReportingModel( + id = reporting.id, + reportingId = reporting.reportingId, + sourceType = reporting.sourceType, + semaphore = semaphoreReference, + controlUnit = controlUnitReference, + sourceName = reporting.sourceName, + targetType = reporting.targetType, + vehicleType = reporting.vehicleType, + targetDetails = reporting.targetDetails, + geom = reporting.geom, + seaFront = reporting.seaFront, + description = reporting.description, + reportType = reporting.reportType, + theme = reporting.theme, + subThemes = reporting.subThemes, + actionTaken = reporting.actionTaken, + isControlRequired = reporting.isControlRequired, + hasNoUnitAvailable = reporting.hasNoUnitAvailable, + createdAt = reporting.createdAt.toInstant(), + validityTime = reporting.validityTime, + isArchived = reporting.isArchived, + isDeleted = reporting.isDeleted, + openBy = reporting.openBy, + mission = missionReference, + attachedToMissionAtUtc = reporting.attachedToMissionAtUtc?.toInstant(), + detachedFromMissionAtUtc = reporting.detachedFromMissionAtUtc?.toInstant(), + attachedEnvActionId = reporting.attachedEnvActionId, + ) } } 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 cf4c1f686..681e42f2a 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 @@ -12,21 +12,21 @@ import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces. import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBMissionRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBReportingRepository import fr.gouv.cacem.monitorenv.infrastructure.database.repositories.interfaces.IDBSemaphoreRepository +import java.time.Instant import org.springframework.dao.DataIntegrityViolationException import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.Modifying import org.springframework.orm.jpa.JpaObjectRetrievalFailureException import org.springframework.stereotype.Repository import org.springframework.transaction.annotation.Transactional -import java.time.Instant @Repository class JpaReportingRepository( - private val dbReportingRepository: IDBReportingRepository, - private val dbMissionRepository: IDBMissionRepository, - private val dbSemaphoreRepository: IDBSemaphoreRepository, - private val dbControlUnitRepository: IDBControlUnitRepository, - private val mapper: ObjectMapper, + private val dbReportingRepository: IDBReportingRepository, + private val dbMissionRepository: IDBMissionRepository, + private val dbSemaphoreRepository: IDBSemaphoreRepository, + private val dbControlUnitRepository: IDBControlUnitRepository, + private val mapper: ObjectMapper, ) : IReportingRepository { @Transactional @@ -40,26 +40,26 @@ class JpaReportingRepository( } override fun findAll( - pageable: Pageable, - reportingType: List?, - seaFronts: List?, - sourcesType: List?, - startedAfter: Instant, - startedBefore: Instant?, - status: List?, + pageable: Pageable, + reportingType: List?, + seaFronts: List?, + sourcesType: List?, + startedAfter: Instant, + startedBefore: Instant?, + status: List?, ): List { val sourcesTypeAsStringArray = sourcesType?.map { it.name } val reportingTypeAsStringArray = reportingType?.map { it.name } return dbReportingRepository.findAll( - pageable, - reportingType = convertToString(reportingTypeAsStringArray), - seaFronts = convertToString(seaFronts), - sourcesType = convertToString(sourcesTypeAsStringArray), - startedAfter = startedAfter, - startedBefore = startedBefore, - status = convertToString(status), - ) - .map { it.toReportingDTO(mapper) } + pageable, + reportingType = convertToString(reportingTypeAsStringArray), + seaFronts = convertToString(seaFronts), + sourcesType = convertToString(sourcesTypeAsStringArray), + startedAfter = startedAfter, + startedBefore = startedBefore, + status = convertToString(status), + ) + .map { it.toReportingDTO(mapper) } } override fun findByMissionId(missionId: Int): List { @@ -71,41 +71,41 @@ class JpaReportingRepository( override fun save(reporting: ReportingEntity): ReportingDTO { return try { val semaphoreReference = - if (reporting.semaphoreId != null) { - dbSemaphoreRepository.getReferenceById( - reporting.semaphoreId, - ) - } else { - null - } + if (reporting.semaphoreId != null) { + dbSemaphoreRepository.getReferenceById( + reporting.semaphoreId, + ) + } else { + null + } val controlUnitReference = - if (reporting.controlUnitId != null) { - dbControlUnitRepository.getReferenceById( - reporting.controlUnitId, - ) - } else { - null - } + if (reporting.controlUnitId != null) { + dbControlUnitRepository.getReferenceById( + reporting.controlUnitId, + ) + } else { + null + } val missionReference = - if (reporting.missionId != null) { - dbMissionRepository.getReferenceById( - reporting.missionId, - ) - } else { - null - } + if (reporting.missionId != null) { + dbMissionRepository.getReferenceById( + reporting.missionId, + ) + } else { + null + } val reportingModel = - ReportingModel.fromReportingEntity( - reporting = reporting, - semaphoreReference = semaphoreReference, - controlUnitReference = controlUnitReference, - missionReference = missionReference, - ) + ReportingModel.fromReportingEntity( + reporting = reporting, + semaphoreReference = semaphoreReference, + controlUnitReference = controlUnitReference, + missionReference = missionReference, + ) dbReportingRepository.saveAndFlush(reportingModel).toReportingDTO(mapper) } catch (e: JpaObjectRetrievalFailureException) { throw NotFoundException( - "Invalid reference to semaphore, control unit or mission: not found in referential", - e, + "Invalid reference to semaphore, control unit or mission: not found in referential", + e, ) } catch (e: DataIntegrityViolationException) { throw NotFoundException("Invalid combination of mission and/or envAction", e) diff --git a/frontend/src/api/missionsAPI.ts b/frontend/src/api/missionsAPI.ts index 06caa8cf3..ffa729418 100644 --- a/frontend/src/api/missionsAPI.ts +++ b/frontend/src/api/missionsAPI.ts @@ -27,9 +27,10 @@ const getSeaFrontsFilter = seaFronts => export const missionsAPI = monitorenvPrivateApi.injectEndpoints({ endpoints: builder => ({ createMission: builder.mutation({ - invalidatesTags: [ + invalidatesTags: (_, __, { attachedReportingIds = [] }) => [ { id: 'LIST', type: 'Missions' }, - { id: 'LIST', type: 'Reportings' } + { id: 'LIST', type: 'Reportings' }, + ...attachedReportingIds.map(reportingId => ({ id: reportingId, type: 'Reportings' as const })) ], query: mission => ({ body: mission, @@ -69,11 +70,12 @@ export const missionsAPI = monitorenvPrivateApi.injectEndpoints({ .join('&') }), updateMission: builder.mutation({ - invalidatesTags: (_, __, { id }) => [ + invalidatesTags: (_, __, { attachedReportingIds = [], id }) => [ { id, type: 'Missions' }, - { id: 'LIST', type: 'Missions' } + { id: 'LIST', type: 'Missions' }, + { id: 'LIST', type: 'Reportings' }, + ...attachedReportingIds.map(reportingId => ({ id: reportingId, type: 'Reportings' as const })) ], - query: ({ id, ...patch }) => ({ body: { id, ...patch }, method: 'PUT', diff --git a/frontend/src/api/reportingsAPI.ts b/frontend/src/api/reportingsAPI.ts index 4c3fda7b8..b4d9e3533 100644 --- a/frontend/src/api/reportingsAPI.ts +++ b/frontend/src/api/reportingsAPI.ts @@ -31,9 +31,10 @@ export const reportingsAPI = monitorenvPrivateApi.injectEndpoints({ }) }), createReporting: build.mutation, Partial>({ - invalidatesTags: [ + invalidatesTags: (_, __, { missionId }) => [ { id: 'LIST', type: 'Reportings' }, - { id: 'LIST', type: 'Missions' } + { id: 'LIST', type: 'Missions' }, + { id: missionId, type: 'Missions' } ], query: reporting => ({ body: reporting, @@ -72,9 +73,11 @@ export const reportingsAPI = monitorenvPrivateApi.injectEndpoints({ transformResponse: (response: ReportingDetailed[]) => ReportingAdapter.setAll(initialState, response) }), updateReporting: build.mutation>({ - invalidatesTags: (_, __, { id }) => [ + invalidatesTags: (_, __, { id, missionId }) => [ { id, type: 'Reportings' }, - { id: 'LIST', type: 'Reportings' } + { id: 'LIST', type: 'Reportings' }, + { id: 'LIST', type: 'Missions' }, + { id: missionId, type: 'Missions' } ], query: ({ id, ...patch }) => ({ body: { id, ...patch }, diff --git a/frontend/src/domain/entities/missions.ts b/frontend/src/domain/entities/missions.ts index 60940530b..49b961d54 100644 --- a/frontend/src/domain/entities/missions.ts +++ b/frontend/src/domain/entities/missions.ts @@ -252,8 +252,8 @@ export type ResourceUnit = { } export type Mission = { - attachedReportingIds?: number[] - attachedReportings?: ReportingDetailed[] + attachedReportingIds: number[] + attachedReportings: ReportingDetailed[] closedBy: string controlUnits: LegacyControlUnit[] endDateTimeUtc?: string diff --git a/frontend/src/domain/entities/reporting.ts b/frontend/src/domain/entities/reporting.ts index 9a53d885b..2b02a61bc 100644 --- a/frontend/src/domain/entities/reporting.ts +++ b/frontend/src/domain/entities/reporting.ts @@ -9,6 +9,7 @@ export type Reporting = { attachedEnvActionId?: string attachedMission?: Mission attachedToMissionAtUtc?: string + controlStatus: ControlStatusEnum controlUnitId?: number createdAt: string description?: string @@ -37,6 +38,18 @@ export type ReportingDetailed = Reporting & { displayedSource: string } +export enum ControlStatusEnum { + CONTROL_TO_BE_DONE = 'CONTROL_TO_BE_DONE', + CONTROL_DONE = 'CONTROL_DONE', + SURVEILLANCE_DONE = 'SURVEILLANCE_DONE' +} + +export enum ControlStatusLabels { + CONTROL_TO_BE_DONE = 'Contrôle à faire', + CONTROL_DONE = 'Contrôle fait', + SURVEILLANCE_DONE = 'Surveillance faite' +} + type TargetDetails = { externalReferenceNumber?: string imo?: string diff --git a/frontend/src/domain/use_cases/reporting/attachMission.ts b/frontend/src/domain/use_cases/reporting/attachMission.ts new file mode 100644 index 000000000..1ce97da79 --- /dev/null +++ b/frontend/src/domain/use_cases/reporting/attachMission.ts @@ -0,0 +1,23 @@ +import { missionsAPI } from '../../../api/missionsAPI' +import { attachMissionToReportingSliceActions } from '../../../features/Reportings/ReportingForm/AttachMission/slice' +import { setToast } from '../../shared_slices/Global' + +export const attachMission = (id: number) => async (dispatch, getState) => { + const { missionId } = getState().attachMissionToReporting + + try { + if (missionId === id) { + return + } + + const response = await dispatch(missionsAPI.endpoints.getMission.initiate(id)) + if ('error' in response) { + throw Error("Erreur à l'ajout du signalement") + } else { + dispatch(attachMissionToReportingSliceActions.setAttachedMission(response.data)) + dispatch(attachMissionToReportingSliceActions.setMissionId(id)) + } + } catch (error) { + dispatch(setToast({ containerId: 'sideWindow', message: error })) + } +} diff --git a/frontend/src/features/Reportings/Layers/MissionToAttach/index.tsx b/frontend/src/features/Reportings/Layers/MissionToAttach/index.tsx index 89f430f7d..5b0f53219 100644 --- a/frontend/src/features/Reportings/Layers/MissionToAttach/index.tsx +++ b/frontend/src/features/Reportings/Layers/MissionToAttach/index.tsx @@ -4,11 +4,11 @@ import { useCallback, useEffect, useMemo, useRef } from 'react' import { useGetMissionsQuery } from '../../../../api/missionsAPI' import { Layers } from '../../../../domain/entities/layers/constants' +import { attachMission } from '../../../../domain/use_cases/reporting/attachMission' import { useAppDispatch } from '../../../../hooks/useAppDispatch' import { useAppSelector } from '../../../../hooks/useAppSelector' import { getMissionZoneFeature } from '../../../map/layers/Missions/missionGeometryHelpers' import { missionWithCentroidStyleFn } from '../../../map/layers/Missions/missions.style' -import { attachMissionToReportingSliceActions } from '../../ReportingForm/AttachMission/slice' import type { BaseMapChildrenProps } from '../../../map/BaseMap' import type { Geometry } from 'ol/geom' @@ -78,7 +78,7 @@ export function MissionToAttachLayer({ map, mapClickEvent }: BaseMapChildrenProp const feature = mapClickEvent?.feature if (feature.getId()?.toString()?.includes(Layers.MISSION_TO_ATTACH_ON_REPORTING.code)) { const { missionId } = feature.getProperties() - dispatch(attachMissionToReportingSliceActions.setMissionId(missionId)) + dispatch(attachMission(missionId)) } } }, [dispatch, mapClickEvent]) diff --git a/frontend/src/features/Reportings/ReportingForm/AttachMission/AttachedMissionCard.tsx b/frontend/src/features/Reportings/ReportingForm/AttachMission/AttachedMissionCard.tsx index 12ad1cf0f..eb1963391 100644 --- a/frontend/src/features/Reportings/ReportingForm/AttachMission/AttachedMissionCard.tsx +++ b/frontend/src/features/Reportings/ReportingForm/AttachMission/AttachedMissionCard.tsx @@ -6,12 +6,14 @@ import { MissionStatusLabel } from '../../../../ui/MissionStatusLabel' import { missionTypesToString } from '../../../../utils/missionTypes' import { StatusActionTag } from '../../components/StatusActionTag' +import type { ControlStatusEnum } from '../../../../domain/entities/reporting' + export function AttachedMissionCard({ - attachedEnvActionId, - attachedMission + attachedMission, + controlStatus }: { - attachedEnvActionId: string | undefined attachedMission: Mission | undefined + controlStatus: ControlStatusEnum }) { if (!attachedMission) { return null @@ -67,7 +69,7 @@ export function AttachedMissionCard({
- +
diff --git a/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx b/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx index c814283fb..2407dde73 100644 --- a/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx +++ b/frontend/src/features/Reportings/ReportingForm/AttachMission/index.tsx @@ -1,12 +1,10 @@ import { Accent, Button, FormikCheckbox, Icon } from '@mtes-mct/monitor-ui' -import { skipToken } from '@reduxjs/toolkit/dist/query' import { useFormikContext } from 'formik' import { useEffect } from 'react' import styled from 'styled-components' import { AttachedMissionCard } from './AttachedMissionCard' import { attachMissionToReportingSliceActions } from './slice' -import { useGetMissionQuery } from '../../../../api/missionsAPI' import { MapInteractionListenerEnum, updateMapInteractionListeners @@ -20,9 +18,7 @@ export function AttachMission({ setIsAttachNewMission }) { const { handleSubmit, setFieldValue, values } = useFormikContext() const dispatch = useAppDispatch() const missionId = useAppSelector(state => state.attachMissionToReporting.missionId) - - const hasMissionAttached = !!values.missionId && !!values.attachedMission && values.attachedMission.id === missionId - const { data: missionToAttach } = useGetMissionQuery(!hasMissionAttached && missionId ? missionId : skipToken) + const attachedMission = useAppSelector(state => state.attachMissionToReporting.attachedMission) const attachMission = () => { dispatch(attachMissionToReportingSliceActions.setInitialAttachedMission(values.attachedMission)) @@ -40,11 +36,11 @@ export function AttachMission({ setIsAttachNewMission }) { } useEffect(() => { - if (missionId !== values.missionId && missionToAttach) { + if (missionId !== values.missionId) { setFieldValue('missionId', missionId) - setFieldValue('attachedMission', missionId ? missionToAttach : undefined) + setFieldValue('attachedMission', attachedMission) } - }, [missionId, setFieldValue, dispatch, missionToAttach, values.missionId]) + }, [missionId, setFieldValue, dispatch, values.missionId, attachedMission]) return !values.missionId ? ( @@ -75,7 +71,7 @@ export function AttachMission({ setIsAttachNewMission }) { Signalement lié à une mission - +