From a5033fb98d2604b2c0bb7a6883af875b71faf535 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Tue, 28 Jan 2025 15:14:40 +0100 Subject: [PATCH 1/3] Fix fleet segment management --- .../exceptions/BackendUsageErrorCode.kt | 3 + .../exceptions/BackendUsageException.kt | 3 +- ...ouldNotUpdateBeaconMalfunctionException.kt | 1 + ...CouldNotUpdateControlObjectiveException.kt | 1 + .../CouldNotUpdateFleetSegmentException.kt | 6 -- .../repositories/FleetSegmentRepository.kt | 4 +- .../dtos/CreateOrUpdateFleetSegmentFields.kt | 19 ---- .../fleet_segment/CreateFleetSegment.kt | 40 +-------- .../fleet_segment/UpdateFleetSegment.kt | 23 +---- .../mission_actions/PatchMissionAction.kt | 2 +- .../use_cases/vessel/GetVesselVoyage.kt | 8 +- .../api/ControllersExceptionHandler.kt | 12 --- .../api/bff/FleetSegmentAdminController.kt | 8 +- .../CreateOrUpdateFleetSegmentDataInput.kt | 36 ++++---- .../repositories/JpaFleetSegmentRepository.kt | 88 +++++++------------ .../interfaces/DBFleetSegmentRepository.kt | 23 ++++- .../fleet_segment/UpdateFleetSegmentUTests.kt | 42 --------- .../bff/FleetSegmentAdminControllerITests.kt | 83 +---------------- .../JpaFleetSegmentRepositoryITests.kt | 66 ++++++++++---- .../back_office/fleet_segment_table.spec.ts | 8 +- frontend/package-lock.json | 18 +++- frontend/package.json | 3 +- frontend/src/features/FleetSegment/apis.ts | 53 +++++------ .../CreateOrEditFleetSegmentModal.tsx | 16 ++-- .../FleetSegmentsBackoffice/index.tsx | 6 +- .../FleetSegmentsBackoffice/schema.ts | 34 +++---- frontend/src/features/FleetSegment/types.ts | 35 ++++---- .../useCases/createFleetSegment.ts | 4 +- .../useCases/updateFleetSegment.ts | 18 ++-- 29 files changed, 248 insertions(+), 415 deletions(-) delete mode 100644 backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateFleetSegmentException.kt delete mode 100644 backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/dtos/CreateOrUpdateFleetSegmentFields.kt delete mode 100644 backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegmentUTests.kt diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageErrorCode.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageErrorCode.kt index 9bd5603c8e..3b32d65db2 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageErrorCode.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageErrorCode.kt @@ -19,6 +19,9 @@ enum class BackendUsageErrorCode { /** Thrown when a resource is expected to exist but doesn't. */ NOT_FOUND, + /** Thrown when a resource could not be updated. */ + COULD_NOT_UPDATE, + /** Thrown when a resource does not found, but it may happen. */ NOT_FOUND_BUT_OK, } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageException.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageException.kt index 1f70677e45..30852a905a 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageException.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/BackendUsageException.kt @@ -15,5 +15,6 @@ package fr.gouv.cnsp.monitorfish.domain.exceptions open class BackendUsageException( val code: BackendUsageErrorCode, final override val message: String? = null, + final override val cause: Throwable? = null, val data: Any? = null, -) : Throwable(code.name) +) : Throwable(code.name, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateBeaconMalfunctionException.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateBeaconMalfunctionException.kt index cec7061055..7a2f95b973 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateBeaconMalfunctionException.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateBeaconMalfunctionException.kt @@ -1,5 +1,6 @@ package fr.gouv.cnsp.monitorfish.domain.exceptions +@Deprecated("Use BackendUsageException with COULD_NOT_UPDATE code") class CouldNotUpdateBeaconMalfunctionException( message: String, cause: Throwable? = null, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateControlObjectiveException.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateControlObjectiveException.kt index b255e338a7..fe978de2cd 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateControlObjectiveException.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateControlObjectiveException.kt @@ -1,5 +1,6 @@ package fr.gouv.cnsp.monitorfish.domain.exceptions +@Deprecated("Use BackendUsageException with COULD_NOT_UPDATE code") class CouldNotUpdateControlObjectiveException( message: String, cause: Throwable? = null, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateFleetSegmentException.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateFleetSegmentException.kt deleted file mode 100644 index 190cc2e9ca..0000000000 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/exceptions/CouldNotUpdateFleetSegmentException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package fr.gouv.cnsp.monitorfish.domain.exceptions - -class CouldNotUpdateFleetSegmentException( - message: String, - cause: Throwable? = null, -) : Throwable(message, cause) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/FleetSegmentRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/FleetSegmentRepository.kt index 993c071ffb..96f027b0be 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/FleetSegmentRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/FleetSegmentRepository.kt @@ -1,7 +1,6 @@ package fr.gouv.cnsp.monitorfish.domain.repositories import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields interface FleetSegmentRepository { // For test purpose @@ -11,8 +10,7 @@ interface FleetSegmentRepository { fun update( segment: String, - fields: CreateOrUpdateFleetSegmentFields, - year: Int, + updatedSegment: FleetSegment, ): FleetSegment fun delete( diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/dtos/CreateOrUpdateFleetSegmentFields.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/dtos/CreateOrUpdateFleetSegmentFields.kt deleted file mode 100644 index ed7bdd25de..0000000000 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/dtos/CreateOrUpdateFleetSegmentFields.kt +++ /dev/null @@ -1,19 +0,0 @@ -package fr.gouv.cnsp.monitorfish.domain.use_cases.dtos - -import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.ScipSpeciesType - -data class CreateOrUpdateFleetSegmentFields( - val segment: String? = null, - val segmentName: String? = null, - val gears: List? = null, - val faoAreas: List? = null, - val targetSpecies: List? = null, - val mainScipSpeciesType: ScipSpeciesType? = null, - val maxMesh: Double? = null, - val minMesh: Double? = null, - val minShareOfTargetSpecies: Double? = null, - val priority: Double? = null, - val vesselTypes: List? = null, - val impactRiskFactor: Double? = null, - val year: Int? = null, -) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/CreateFleetSegment.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/CreateFleetSegment.kt index 237959e8f0..57f4be463b 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/CreateFleetSegment.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/CreateFleetSegment.kt @@ -3,48 +3,10 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment import fr.gouv.cnsp.monitorfish.domain.repositories.FleetSegmentRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields @UseCase class CreateFleetSegment( private val fleetSegmentRepository: FleetSegmentRepository, ) { - fun execute(fields: CreateOrUpdateFleetSegmentFields): FleetSegment { - require(fields.segment != null) { - "`segment` must be provided" - } - - require(fields.year != null) { - "`year` must be provided" - } - - require(fields.priority != null) { - "`priority` must be provided" - } - - require(fields.vesselTypes != null) { - "`vesselTypes` must be provided" - } - - val newSegment = - fields.let { - FleetSegment( - segment = it.segment!!, - segmentName = it.segmentName ?: "", - gears = it.gears ?: listOf(), - faoAreas = it.faoAreas ?: listOf(), - targetSpecies = it.targetSpecies ?: listOf(), - impactRiskFactor = it.impactRiskFactor ?: 0.0, - year = it.year!!, - mainScipSpeciesType = fields.mainScipSpeciesType, - maxMesh = fields.maxMesh, - minMesh = fields.minMesh, - minShareOfTargetSpecies = fields.minShareOfTargetSpecies, - priority = fields.priority, - vesselTypes = fields.vesselTypes, - ) - } - - return fleetSegmentRepository.save(newSegment) - } + fun execute(newSegment: FleetSegment): FleetSegment = fleetSegmentRepository.save(newSegment) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegment.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegment.kt index b968240336..256fb5e4db 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegment.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegment.kt @@ -2,31 +2,16 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment -import fr.gouv.cnsp.monitorfish.domain.exceptions.CouldNotUpdateFleetSegmentException +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageException import fr.gouv.cnsp.monitorfish.domain.repositories.FleetSegmentRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields @UseCase class UpdateFleetSegment( private val fleetSegmentRepository: FleetSegmentRepository, ) { - @Throws(CouldNotUpdateFleetSegmentException::class, IllegalArgumentException::class) + @Throws(BackendUsageException::class, IllegalArgumentException::class) fun execute( segment: String, - fields: CreateOrUpdateFleetSegmentFields, - year: Int, - ): FleetSegment { - require( - fields.segment != null || - fields.segmentName != null || - fields.faoAreas != null || - fields.gears != null || - fields.impactRiskFactor != null || - fields.targetSpecies != null, - ) { - "No value to update" - } - - return fleetSegmentRepository.update(segment, fields, year) - } + updatedSegment: FleetSegment, + ): FleetSegment = fleetSegmentRepository.update(segment, updatedSegment) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/PatchMissionAction.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/PatchMissionAction.kt index 17e25d1cbf..96257bf814 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/PatchMissionAction.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/PatchMissionAction.kt @@ -24,6 +24,6 @@ class PatchMissionAction( missionActionsRepository.save(updatedMissionAction) } catch (e: Exception) { - throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND, "Action $id not found", e) + throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND, message = "Action $id not found", cause = e) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVesselVoyage.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVesselVoyage.kt index 76fec2f64b..a3cb663bb8 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVesselVoyage.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVesselVoyage.kt @@ -69,14 +69,14 @@ class GetVesselVoyage( } catch (e: IllegalArgumentException) { throw BackendUsageException( BackendUsageErrorCode.NOT_FOUND_BUT_OK, - "Could not fetch voyage for request \"${voyageRequest}\"", - e, + message = "Could not fetch voyage for request \"${voyageRequest}\"", + cause = e, ) } catch (e: NoLogbookFishingTripFound) { throw BackendUsageException( BackendUsageErrorCode.NOT_FOUND_BUT_OK, - "Could not fetch voyage for request \"${voyageRequest}\"", - e, + message = "Could not fetch voyage for request \"${voyageRequest}\"", + cause = e, ) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/ControllersExceptionHandler.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/ControllersExceptionHandler.kt index c851dcfe62..b24f51b9e3 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/ControllersExceptionHandler.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/ControllersExceptionHandler.kt @@ -82,18 +82,6 @@ class ControllersExceptionHandler( return ApiError(IllegalArgumentException(e.message.toString(), e)) } - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(CouldNotUpdateFleetSegmentException::class) - fun handleCouldNotUpdateFleetSegmentException(e: Exception): ApiError { - logger.error(e.message, e.cause) - - if (sentryConfig.enabled == true) { - Sentry.captureException(e) - } - - return ApiError(e) - } - @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MissingServletRequestParameterException::class) fun handleNoParameter(e: MissingServletRequestParameterException): MissingParameterApiError { diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminController.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminController.kt index 78a7114904..a2be80d2b3 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminController.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminController.kt @@ -23,9 +23,6 @@ class FleetSegmentAdminController( @PutMapping(value = [""], consumes = ["application/json"]) @Operation(summary = "Update a fleet segment") fun updateFleetSegment( - @Parameter(description = "Year") - @RequestParam(name = "year") - year: Int, @Parameter(description = "Segment") @RequestParam(name = "segment") segment: String, @@ -35,8 +32,7 @@ class FleetSegmentAdminController( val updatedFleetSegment = updateFleetSegment.execute( segment = segment, - fields = createOrUpdateFleetSegmentData.toCreateOrUpdateFleetSegmentFields(), - year = year, + updatedSegment = createOrUpdateFleetSegmentData.toFleetSegment(), ) return FleetSegmentDataOutput.fromFleetSegment(updatedFleetSegment) @@ -63,7 +59,7 @@ class FleetSegmentAdminController( @RequestBody newFleetSegmentData: CreateOrUpdateFleetSegmentDataInput, ): FleetSegmentDataOutput { - val createdFleetSegment = createFleetSegment.execute(newFleetSegmentData.toCreateOrUpdateFleetSegmentFields()) + val createdFleetSegment = createFleetSegment.execute(newFleetSegmentData.toFleetSegment()) return FleetSegmentDataOutput.fromFleetSegment(createdFleetSegment) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/input/CreateOrUpdateFleetSegmentDataInput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/input/CreateOrUpdateFleetSegmentDataInput.kt index c9f95fdb34..1ea50f9395 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/input/CreateOrUpdateFleetSegmentDataInput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/input/CreateOrUpdateFleetSegmentDataInput.kt @@ -1,10 +1,10 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.input +import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.ScipSpeciesType -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields data class CreateOrUpdateFleetSegmentDataInput( - val segment: String?, + val segment: String, val segmentName: String?, val gears: List?, val faoAreas: List?, @@ -16,22 +16,22 @@ data class CreateOrUpdateFleetSegmentDataInput( val priority: Double, val vesselTypes: List, val impactRiskFactor: Double?, - val year: Int?, + val year: Int, ) { - fun toCreateOrUpdateFleetSegmentFields() = - CreateOrUpdateFleetSegmentFields( - segment = segment, - segmentName = segmentName, - gears = gears, - faoAreas = faoAreas, - targetSpecies = targetSpecies, - mainScipSpeciesType = mainScipSpeciesType, - maxMesh = maxMesh, - minMesh = minMesh, - minShareOfTargetSpecies = minShareOfTargetSpecies, - priority = priority, - vesselTypes = vesselTypes, - impactRiskFactor = impactRiskFactor, - year = year, + fun toFleetSegment() = + FleetSegment( + segment = this.segment, + segmentName = this.segmentName ?: "", + gears = this.gears ?: listOf(), + faoAreas = this.faoAreas ?: listOf(), + targetSpecies = this.targetSpecies ?: listOf(), + impactRiskFactor = this.impactRiskFactor ?: 0.0, + year = this.year, + mainScipSpeciesType = this.mainScipSpeciesType, + maxMesh = this.maxMesh, + minMesh = this.minMesh, + minShareOfTargetSpecies = this.minShareOfTargetSpecies, + priority = this.priority, + vesselTypes = this.vesselTypes, ) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt index 8f3b7bde47..23fd15c5cf 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt @@ -1,10 +1,10 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageException import fr.gouv.cnsp.monitorfish.domain.exceptions.CouldNotDeleteException -import fr.gouv.cnsp.monitorfish.domain.exceptions.CouldNotUpdateFleetSegmentException import fr.gouv.cnsp.monitorfish.domain.repositories.FleetSegmentRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.FleetSegmentEntity import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces.DBFleetSegmentRepository import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.utils.toSqlArrayString @@ -34,65 +34,37 @@ class JpaFleetSegmentRepository( @Transactional override fun update( segment: String, - fields: CreateOrUpdateFleetSegmentFields, - year: Int, + updatedSegment: FleetSegment, ): FleetSegment { + val fleetSegmentEntity = FleetSegmentEntity.fromFleetSegment(updatedSegment) + + // The list properties needs to be formatted + val escapedFleetSegmentEntity = + fleetSegmentEntity.copy( + vesselTypes = toSqlArrayString(fleetSegmentEntity.vesselTypes)?.let { listOf(it) }, + gears = toSqlArrayString(fleetSegmentEntity.gears)?.let { listOf(it) }, + faoAreas = toSqlArrayString(fleetSegmentEntity.faoAreas)?.let { listOf(it) }, + targetSpecies = toSqlArrayString(fleetSegmentEntity.targetSpecies)?.let { listOf(it) }, + ) + try { - fields.segmentName?.let { - dbFleetSegmentRepository.updateSegmentName(segment, it, year) - } - - fields.gears?.let { - dbFleetSegmentRepository.updateGears(segment, it.toArrayString(), year) - } - - fields.faoAreas?.let { - dbFleetSegmentRepository.updateFAOAreas(segment, it.toArrayString(), year) - } - - fields.targetSpecies?.let { - dbFleetSegmentRepository.updateTargetSpecies(segment, it.toArrayString(), year) - } - - fields.mainScipSpeciesType?.let { - dbFleetSegmentRepository.updateMainScipSpeciesType(segment, it.name, year) - } - - fields.maxMesh?.let { - dbFleetSegmentRepository.updateMaxMesh(segment, it, year) - } - - fields.minMesh?.let { - dbFleetSegmentRepository.updateMinMesh(segment, it, year) - } - - fields.minShareOfTargetSpecies?.let { - dbFleetSegmentRepository.updateMinShareOfTargetSpecies(segment, it, year) - } - - fields.priority?.let { - dbFleetSegmentRepository.updatePriority(segment, it, year) - } - - fields.vesselTypes?.let { - dbFleetSegmentRepository.updateVesselTypes(segment, it, year) - } - - fields.impactRiskFactor?.let { - dbFleetSegmentRepository.updateImpactRiskFactor(segment, it, year) - } - - fields.segment?.let { - dbFleetSegmentRepository.updateSegment(segment, it, year) - } - - return fields.segment?.let { - dbFleetSegmentRepository.findBySegmentAndYearEquals(it, year).toFleetSegment() - } ?: run { - dbFleetSegmentRepository.findBySegmentAndYearEquals(segment, year).toFleetSegment() - } + dbFleetSegmentRepository.updateFleetSegment(segment, escapedFleetSegmentEntity) + } catch (e: Throwable) { + throw BackendUsageException( + code = BackendUsageErrorCode.COULD_NOT_UPDATE, + message = "Could not update fleet segment", + cause = e, + ) + } + + return try { + dbFleetSegmentRepository + .findBySegmentAndYearEquals( + updatedSegment.segment, + updatedSegment.year, + ).toFleetSegment() } catch (e: Throwable) { - throw CouldNotUpdateFleetSegmentException("Could not update fleet segment: ${e.message}", e) + throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBFleetSegmentRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBFleetSegmentRepository.kt index 9690fca4f6..36d3a1f37d 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBFleetSegmentRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBFleetSegmentRepository.kt @@ -45,13 +45,28 @@ interface DBFleetSegmentRepository : CrudRepository { @Modifying(clearAutomatically = true) @Query( - value = "UPDATE fleet_segments SET segment = :nextSegment WHERE segment = :segment and year = :year", nativeQuery = true, + value = """ + UPDATE fleet_segments SET + segment = :#{#fleetSegment.segment}, + segment_name = :#{#fleetSegment.segmentName}, + main_scip_species_type = CAST(:#{#fleetSegment.mainScipSpeciesType} AS scip_species_type), + priority = :#{#fleetSegment.priority}, + max_mesh = :#{#fleetSegment.maxMesh}, + min_mesh = :#{#fleetSegment.minMesh}, + min_share_of_target_species = :#{#fleetSegment.minShareOfTargetSpecies}, + vessel_types = CAST(:#{#fleetSegment.vesselTypes} AS varchar[]), + gears = CAST(:#{#fleetSegment.gears} AS varchar(3)[]), + fao_areas = CAST(:#{#fleetSegment.faoAreas} AS varchar(15)[]), + target_species = CAST(:#{#fleetSegment.targetSpecies} AS varchar(3)[]), + impact_risk_factor = :#{#fleetSegment.impactRiskFactor}, + year = :#{#fleetSegment.year} + WHERE segment = :segment and year = :#{#fleetSegment.year} + """, ) - fun updateSegment( + fun updateFleetSegment( segment: String, - nextSegment: String, - year: Int, + fleetSegment: FleetSegmentEntity, ) @Modifying(clearAutomatically = true) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegmentUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegmentUTests.kt deleted file mode 100644 index f803771937..0000000000 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fleet_segment/UpdateFleetSegmentUTests.kt +++ /dev/null @@ -1,42 +0,0 @@ -package fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment - -import fr.gouv.cnsp.monitorfish.domain.repositories.FleetSegmentRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.catchThrowable -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.Mockito -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.test.context.junit.jupiter.SpringExtension - -@ExtendWith(SpringExtension::class) -class UpdateFleetSegmentUTests { - @MockBean - private lateinit var fleetSegmentRepository: FleetSegmentRepository - - @Test - fun `execute Should throw an exception When there is no fields given`() { - // When - val throwable = - catchThrowable { - UpdateFleetSegment(fleetSegmentRepository).execute("SEGMENT", CreateOrUpdateFleetSegmentFields(), 2021) - } - - // Then - assertThat(throwable).isNotNull - assertThat(throwable.message).isEqualTo("No value to update") - } - - @Test - fun `execute Should update repository When a field is given`() { - // Given - val fields = CreateOrUpdateFleetSegmentFields(targetSpecies = listOf("AMZ", "HKE")) - - // When - UpdateFleetSegment(fleetSegmentRepository).execute("SEGMENT", fields, 2021) - - // Then - Mockito.verify(fleetSegmentRepository).update("SEGMENT", fields, 2021) - } -} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminControllerITests.kt index 09a0fb63b4..6ac4389b39 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/FleetSegmentAdminControllerITests.kt @@ -2,16 +2,13 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.bff import com.fasterxml.jackson.databind.ObjectMapper import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.eq import fr.gouv.cnsp.monitorfish.config.SentryConfig import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.* import fr.gouv.cnsp.monitorfish.infrastructure.api.input.CreateOrUpdateFleetSegmentDataInput import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test import org.mockito.BDDMockito.given -import org.mockito.Mockito import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest @@ -54,7 +51,7 @@ class FleetSegmentAdminControllerITests { @Test fun `Should update a fleet segment`() { // Given - given(this.updateFleetSegment.execute(any(), any(), eq(2021))) + given(this.updateFleetSegment.execute(any(), any())) .willReturn( FleetSegment( segment = "A_SEGMENT/WITH/SLASH", @@ -76,12 +73,12 @@ class FleetSegmentAdminControllerITests { // When api .perform( - put("/bff/v1/admin/fleet_segments?year=2021&segment=A_SEGMENT/WITH/SLASH") + put("/bff/v1/admin/fleet_segments?segment=A_SEGMENT/WITH/SLASH") .content( objectMapper.writeValueAsString( CreateOrUpdateFleetSegmentDataInput( gears = listOf("OTB", "OTC"), - segment = null, + segment = "A_SEGMENT", segmentName = null, faoAreas = null, targetSpecies = null, @@ -92,7 +89,7 @@ class FleetSegmentAdminControllerITests { priority = 0.0, vesselTypes = listOf(), impactRiskFactor = null, - year = null, + year = 2021, ), ), ).contentType(MediaType.APPLICATION_JSON), @@ -101,17 +98,6 @@ class FleetSegmentAdminControllerITests { .andExpect(status().isOk) .andExpect(jsonPath("$.segment", equalTo("A_SEGMENT/WITH/SLASH"))) .andExpect(jsonPath("$.gears[0]", equalTo("OTB"))) - - Mockito.verify(updateFleetSegment).execute( - segment = "A_SEGMENT/WITH/SLASH", - fields = - CreateOrUpdateFleetSegmentFields( - gears = listOf("OTB", "OTC"), - priority = 0.0, - vesselTypes = listOf(), - ), - year = 2021, - ) } @Test @@ -180,66 +166,5 @@ class FleetSegmentAdminControllerITests { ) // Then .andExpect(status().isCreated) - - Mockito.verify(createFleetSegment).execute( - CreateOrUpdateFleetSegmentFields( - segment = "SEGMENT", - gears = listOf("OTB", "OTC"), - year = 2022, - segmentName = "", - priority = 1.2, - vesselTypes = listOf(), - faoAreas = listOf(), - targetSpecies = listOf(), - impactRiskFactor = 1.2, - mainScipSpeciesType = null, - maxMesh = null, - minMesh = null, - minShareOfTargetSpecies = null, - ), - ) - } - - @Test - fun `Should throw an exception When no year given to create a fleet segment`() { - // Given - given(createFleetSegment.execute(any())) - .willThrow(IllegalArgumentException("`year` must be provided")) - - // When - api - .perform( - post("/bff/v1/admin/fleet_segments") - .content( - objectMapper.writeValueAsString( - CreateOrUpdateFleetSegmentDataInput( - segment = "SEGMENT", - gears = listOf("OTB", "OTC"), - segmentName = null, - faoAreas = null, - targetSpecies = null, - mainScipSpeciesType = null, - maxMesh = null, - minMesh = null, - minShareOfTargetSpecies = null, - priority = 0.0, - vesselTypes = listOf(), - impactRiskFactor = null, - year = null, - ), - ), - ).contentType(MediaType.APPLICATION_JSON), - ) - // Then - .andExpect(status().isBadRequest) - - Mockito.verify(createFleetSegment).execute( - CreateOrUpdateFleetSegmentFields( - segment = "SEGMENT", - gears = listOf("OTB", "OTC"), - priority = 0.0, - vesselTypes = listOf(), - ), - ) } } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt index f413f983ee..53d9929410 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt @@ -1,7 +1,6 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment -import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegmentFields import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -27,7 +26,6 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { @BeforeEach fun setup() { - cacheManager.getCache("fleet_segments")?.clear() cacheManager.getCache("current_segments")?.clear() cacheManager.getCache("segments_by_year")?.clear() } @@ -55,14 +53,17 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { val fleetSegments = jpaFleetSegmentRepository.findAllByYear(currentYear).sortedBy { it.segment } assertThat(fleetSegments).hasSize(67) - assertThat(fleetSegments.first().segment).isEqualTo("ATL01") + val previous = fleetSegments.first() + assertThat(previous.segment).isEqualTo("ATL01") // When val updatedFleetSegment = jpaFleetSegmentRepository.update( "ATL01", - CreateOrUpdateFleetSegmentFields("NEXT_ATL01", "A segment name"), - currentYear, + previous.copy( + segment = "NEXT_ATL01", + segmentName = "A segment name", + ), ) // Then @@ -79,14 +80,16 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { val fleetSegments = jpaFleetSegmentRepository.findAllByYear(currentYear).sortedBy { it.segment } assertThat(fleetSegments).hasSize(67) - assertThat(fleetSegments.first().segmentName).isEqualTo("ATL01") + val previous = fleetSegments.first() + assertThat(previous.segmentName).isEqualTo("ATL01") // When val updatedFleetSegment = jpaFleetSegmentRepository.update( "ATL01", - CreateOrUpdateFleetSegmentFields(segmentName = "All Trawls 676"), - currentYear, + previous.copy( + segmentName = "All Trawls 676", + ), ) // Then @@ -102,8 +105,9 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { val fleetSegments = jpaFleetSegmentRepository.findAllByYear(currentYear).sortedBy { it.segment } assertThat(fleetSegments).hasSize(67) - assertThat(fleetSegments.first().segment).isEqualTo("ATL01") - assertThat(fleetSegments.first().gears).isEqualTo( + val previous = fleetSegments.first() + assertThat(previous.segment).isEqualTo("ATL01") + assertThat(previous.gears).isEqualTo( listOf("OTM", "PTM"), ) @@ -111,8 +115,9 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { val updatedFleetSegment = jpaFleetSegmentRepository.update( "ATL01", - CreateOrUpdateFleetSegmentFields(gears = listOf("OTB", "DOF")), - currentYear, + previous.copy( + gears = listOf("OTB", "DOF"), + ), ) // Then @@ -129,15 +134,17 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { val fleetSegments = jpaFleetSegmentRepository.findAllByYear(currentYear).sortedBy { it.segment } assertThat(fleetSegments).hasSize(67) - assertThat(fleetSegments.first().segment).isEqualTo("ATL01") - assertThat(fleetSegments.first().faoAreas).isEqualTo(listOf("27.7", "27.8", "27.9", "27.10", "34.1.2")) + val previous = fleetSegments.first() + assertThat(previous.segment).isEqualTo("ATL01") + assertThat(previous.faoAreas).isEqualTo(listOf("27.7", "27.8", "27.9", "27.10", "34.1.2")) // When val updatedFleetSegment = jpaFleetSegmentRepository.update( "ATL01", - CreateOrUpdateFleetSegmentFields(faoAreas = listOf("67.6.6", "67.6.7")), - currentYear, + previous.copy( + faoAreas = listOf("67.6.6", "67.6.7"), + ), ) // Then @@ -145,6 +152,33 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { assertThat(updatedFleetSegment.faoAreas).isEqualTo(listOf("67.6.6", "67.6.7")) } + @Test + @Transactional + fun `update Should update fleet segment vessel types`() { + // Given + val currentYear = ZonedDateTime.now().year + cacheManager.getCache("segments_by_year")?.clear() + val fleetSegments = jpaFleetSegmentRepository.findAllByYear(currentYear).sortedBy { it.segment } + + assertThat(fleetSegments).hasSize(67) + val previous = fleetSegments.first() + assertThat(fleetSegments.first().segment).isEqualTo("ATL01") + assertThat(fleetSegments.first().vesselTypes).isEqualTo(listOf()) + + // When + val updatedFleetSegment = + jpaFleetSegmentRepository.update( + "ATL01", + previous.copy( + vesselTypes = listOf("Chalutier congélateur"), + ), + ) + + // Then + assertThat(updatedFleetSegment.segment).isEqualTo("ATL01") + assertThat(updatedFleetSegment.vesselTypes).isEqualTo(listOf("Chalutier congélateur")) + } + @Test @Transactional fun `create Should insert a new fleet segment`() { diff --git a/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts b/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts index 838382b70e..4443ac7c34 100644 --- a/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts +++ b/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts @@ -40,11 +40,11 @@ context('BackOffice > Fleet Segments Table', () => { cy.intercept('PUT', `/bff/v1/admin/fleet_segments?year=${currentYear}&segment=ATL01`).as('updateFleetSegment') cy.get('[aria-rowindex="2"]').find('[title="Editer la ligne"]').click() - cy.fill('Nom', 'ATL0036') + cy.fill('Code', 'ATL0036') cy.fill('Note d’impact', 1.2) cy.fill('Maillage min.', 50) cy.fill('Maillage max.', 100) - cy.fill('Description', 'All Trawls 45') + cy.fill('Nom', 'All Trawls 45') cy.fill('Engins', ['DRM', 'FAG']) cy.fill('Espèces ciblées', ['COD']) cy.fill('Zones FAO', ['21.0.A']) @@ -100,9 +100,9 @@ context('BackOffice > Fleet Segments Table', () => { // When cy.clickButton('Ajouter un segment') - cy.fill('Nom', 'ABC123') + cy.fill('Code', 'ABC123') cy.fill('Note d’impact', 2.7) - cy.fill('Description', 'Malotru’s segment') + cy.fill('Nom', 'Malotru’s segment') cy.fill('Engins', ['DHS', 'FCN']) cy.fill('Zones FAO', ['21.1.A', '21.1.B']) cy.fill('Maillage min.', 50) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 55b8d6e474..cc363863c7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -61,7 +61,8 @@ "uuid": "10.0.0", "worker-loader": "3.0.8", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", - "yup": "1.6.1" + "yup": "1.6.1", + "zod": "3.24.1" }, "devDependencies": { "@faker-js/faker": "9.3.0", @@ -5451,6 +5452,15 @@ "devtools-protocol": "*" } }, + "node_modules/chromium-bidi/node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/ci-info": { "version": "3.9.0", "dev": true, @@ -17441,9 +17451,9 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "dev": true, - "license": "MIT", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/package.json b/frontend/package.json index 4c4ea7cd82..7f7405fa8d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -86,7 +86,8 @@ "uuid": "10.0.0", "worker-loader": "3.0.8", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", - "yup": "1.6.1" + "yup": "1.6.1", + "zod": "^3.24.1" }, "devDependencies": { "@faker-js/faker": "9.3.0", diff --git a/frontend/src/features/FleetSegment/apis.ts b/frontend/src/features/FleetSegment/apis.ts index 2098d1ccc7..1205417f3d 100644 --- a/frontend/src/features/FleetSegment/apis.ts +++ b/frontend/src/features/FleetSegment/apis.ts @@ -1,9 +1,10 @@ import { monitorfishApi, monitorfishApiKy } from '@api/api' +import { FleetSegmentSchema } from '@features/FleetSegment/types' import { MissionAction } from '@features/Mission/missionAction.types' import { FrontendApiError } from '@libs/FrontendApiError' import { customDayjs } from '@mtes-mct/monitor-ui' -import type { FleetSegment, UpdateFleetSegment } from '@features/FleetSegment/types' +import type { FleetSegment } from '@features/FleetSegment/types' export type ComputeFleetSegmentsParams = { faoAreas: string[] @@ -13,6 +14,11 @@ export type ComputeFleetSegmentsParams = { year: number } +export type UpdateFleetSegmentParams = { + segment: string + updatedSegment: FleetSegment +} + export const fleetSegmentApi = monitorfishApi.injectEndpoints({ endpoints: builder => ({ computeFleetSegments: builder.query({ @@ -22,7 +28,9 @@ export const fleetSegmentApi = monitorfishApi.injectEndpoints({ url: `/fleet_segments/compute` }), transformResponse: (baseQueryReturnValue: FleetSegment[]) => - baseQueryReturnValue.sort((a, b) => a.segment.localeCompare(b.segment)) + baseQueryReturnValue + .map(segment => FleetSegmentSchema.parse(segment)) + .sort((a, b) => a.segment.localeCompare(b.segment)) }), getFleetSegments: builder.query({ providesTags: () => [{ type: 'FleetSegments' }], @@ -32,7 +40,22 @@ export const fleetSegmentApi = monitorfishApi.injectEndpoints({ return `fleet_segments/${controlledYear}` }, transformResponse: (baseQueryReturnValue: FleetSegment[]) => - baseQueryReturnValue.sort((a, b) => a.segment.localeCompare(b.segment)) + baseQueryReturnValue + .map(segment => FleetSegmentSchema.parse(segment)) + .sort((a, b) => a.segment.localeCompare(b.segment)) + }), + updateFleetSegment: builder.query({ + query: params => { + const updatedSegment = FleetSegmentSchema.parse(params.updatedSegment) + + return { + body: updatedSegment, + method: 'PUT', + url: `/admin/fleet_segments?segment=${params.segment}` + } + }, + transformErrorResponse: response => new FrontendApiError(UPDATE_FLEET_SEGMENT_ERROR_MESSAGE, response), + transformResponse: (baseQueryReturnValue: FleetSegment) => FleetSegmentSchema.parse(baseQueryReturnValue) }) }) }) @@ -47,27 +70,6 @@ export const GET_FLEET_SEGMENT_YEAR_ENTRIES_ERROR_MESSAGE = export const ADD_FLEET_SEGMENT_YEAR_ERROR_MESSAGE = "Nous n'avons pas pu ajouter une nouvelle année de segments de flotte" -/** - * Update a fleet segment - * - * @throws {@link FrontendApiError} - */ -async function updateFleetSegmentFromAPI( - segment: string, - year: number, - updatedFields: UpdateFleetSegment -): Promise { - try { - return await monitorfishApiKy - .put(`/bff/v1/admin/fleet_segments?year=${year}&segment=${segment}`, { - json: updatedFields - }) - .json() - } catch (err) { - throw new FrontendApiError(UPDATE_FLEET_SEGMENT_ERROR_MESSAGE, (err as FrontendApiError).originalError) - } -} - /** * Delete a fleet segment * @@ -88,7 +90,7 @@ async function deleteFleetSegmentFromAPI(segment: string, year: number): Promise * * @throws {@link FrontendApiError} */ -async function createFleetSegmentFromAPI(segmentFields: UpdateFleetSegment): Promise { +async function createFleetSegmentFromAPI(segmentFields: FleetSegment): Promise { try { return await monitorfishApiKy .post('/bff/v1/admin/fleet_segments', { @@ -127,7 +129,6 @@ async function getFleetSegmentYearEntriesFromAPI(): Promise { } export { - updateFleetSegmentFromAPI, deleteFleetSegmentFromAPI, createFleetSegmentFromAPI, getFleetSegmentYearEntriesFromAPI, diff --git a/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/CreateOrEditFleetSegmentModal.tsx b/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/CreateOrEditFleetSegmentModal.tsx index f779d01ad5..adab60ce18 100644 --- a/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/CreateOrEditFleetSegmentModal.tsx +++ b/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/CreateOrEditFleetSegmentModal.tsx @@ -18,13 +18,13 @@ import styled from 'styled-components' import { StyledModalHeader } from '../../../commonComponents/StyledModalHeader' import { ScipSpeciesType } from '../../types' -import type { FleetSegment, UpdateFleetSegment } from '../../types' +import type { FleetSegment } from '../../types' type CreateOrEditFleetSegmentModalProps = Readonly<{ faoAreasList: any onCancel: () => void - onCreate: (nextFleetSegment: UpdateFleetSegment) => void - onUpdate: (segment: string, year: number, nextFleetSegment: UpdateFleetSegment) => Promise + onCreate: (nextFleetSegment: FleetSegment) => void + onUpdate: (segment: string, nextFleetSegment: FleetSegment) => Promise updatedFleetSegment: FleetSegment | undefined year: number }> @@ -39,7 +39,7 @@ export function CreateOrEditFleetSegmentModal({ const gearsFAOList = useMainAppSelector(state => state.gear.gears) const speciesFAOList = useMainAppSelector(state => state.species.species) - const initialValues = useMemo(() => { + const initialValues: FleetSegment = useMemo(() => { if (updatedFleetSegment) { return updatedFleetSegment } @@ -53,7 +53,7 @@ export function CreateOrEditFleetSegmentModal({ minMesh: undefined, minShareOfTargetSpecies: undefined, priority: 0, - segment: undefined, + segment: '', segmentName: undefined, targetSpecies: [], vesselTypes: [], @@ -68,7 +68,7 @@ export function CreateOrEditFleetSegmentModal({ const handleSubmit = nextFleetSegment => { if (updatedFleetSegment) { - onUpdate(updatedFleetSegment.segment, year, nextFleetSegment) + onUpdate(updatedFleetSegment.segment, nextFleetSegment) return } @@ -90,7 +90,7 @@ export function CreateOrEditFleetSegmentModal({ - + @@ -127,7 +127,7 @@ export function CreateOrEditFleetSegmentModal({ - + { - const nextFleetSegments = await dispatch(updateFleetSegment(segment, _year, nextFleetSegment, fleetSegments)) + async (segment: string, nextFleetSegment: FleetSegment) => { + const nextFleetSegments = await dispatch(updateFleetSegment(segment, nextFleetSegment, fleetSegments)) if (nextFleetSegments) { setFleetSegments(nextFleetSegments) closeCreateOrEditFleetSegmentModal() diff --git a/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/schema.ts b/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/schema.ts index 2015fedf39..7a6ea80f7d 100644 --- a/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/schema.ts +++ b/frontend/src/features/FleetSegment/components/FleetSegmentsBackoffice/schema.ts @@ -1,19 +1,23 @@ -import { array, number, object, string } from 'yup' +import { ScipSpeciesType } from '@features/FleetSegment/types' +import { array, number, object, ObjectSchema, string, mixed } from 'yup' -export const FLEET_SEGMENT_FORM_SCHEMA = object().shape({ - faoAreas: array(string()), - gears: array(string()), - impactRiskFactor: number().min(1).max(4), - mainScipSpeciesType: string(), - maxMesh: number(), - minMesh: number(), - minShareOfTargetSpecies: number(), - priority: number().required('Veuillez renseigner une priorité.'), +import type { FleetSegment } from '@features/FleetSegment/types' + +export const FLEET_SEGMENT_FORM_SCHEMA: ObjectSchema = object({ + faoAreas: array(string().required()).required().default([]), + gears: array(string().required()).required().default([]), + impactRiskFactor: number().min(1).max(4).default(undefined), + mainScipSpeciesType: mixed().oneOf(Object.values(ScipSpeciesType)).default(undefined), + maxMesh: number().default(undefined), + minMesh: number().default(undefined), + minShareOfTargetSpecies: number().default(undefined), + priority: number().required('Veuillez renseigner une priorité.').default(undefined), segment: string() .required('Veuillez renseigner un nom de segment valide (sans espace).') - .transform((value, originalValue) => (/\s/.test(originalValue) ? undefined : value)), - segmentName: string(), - targetSpecies: array(string()), - vesselTypes: array(string()), - year: number().required() + .transform((value, originalValue) => (/\s/.test(originalValue) ? undefined : value)) + .default(undefined), + segmentName: string().default(undefined), + targetSpecies: array(string().required()).required().default([]), + vesselTypes: array(string().required()).required().default([]), + year: number().required().default(undefined) }) diff --git a/frontend/src/features/FleetSegment/types.ts b/frontend/src/features/FleetSegment/types.ts index 0bf63f240c..9fef72d34d 100644 --- a/frontend/src/features/FleetSegment/types.ts +++ b/frontend/src/features/FleetSegment/types.ts @@ -1,5 +1,4 @@ -import type { Undefine } from '@mtes-mct/monitor-ui' -import type { Float } from 'type-fest' +import { z } from 'zod' export enum ScipSpeciesType { DEMERSAL = 'DEMERSAL', @@ -8,20 +7,20 @@ export enum ScipSpeciesType { TUNA = 'TUNA' } -export type FleetSegment = { - faoAreas: string[] | undefined - gears: string[] | undefined - impactRiskFactor: Float | undefined - mainScipSpeciesType: ScipSpeciesType | undefined - maxMesh: number | undefined - minMesh: number | undefined - minShareOfTargetSpecies: number | undefined - priority: number - segment: string - segmentName: string | undefined - targetSpecies: string[] | undefined - vesselTypes: string[] - year: number -} +export const FleetSegmentSchema = z.object({ + faoAreas: z.array(z.string()), + gears: z.array(z.string()), + impactRiskFactor: z.number().optional(), + mainScipSpeciesType: z.nativeEnum(ScipSpeciesType).optional(), + maxMesh: z.number().optional(), + minMesh: z.number().optional(), + minShareOfTargetSpecies: z.number().optional(), + priority: z.number(), + segment: z.string(), + segmentName: z.string().optional(), + targetSpecies: z.array(z.string()), + vesselTypes: z.array(z.string()), + year: z.number() +}) -export type UpdateFleetSegment = Undefine +export type FleetSegment = z.infer diff --git a/frontend/src/features/FleetSegment/useCases/createFleetSegment.ts b/frontend/src/features/FleetSegment/useCases/createFleetSegment.ts index 483be2ac24..38b2a4f06f 100644 --- a/frontend/src/features/FleetSegment/useCases/createFleetSegment.ts +++ b/frontend/src/features/FleetSegment/useCases/createFleetSegment.ts @@ -2,13 +2,13 @@ import { createFleetSegmentFromAPI } from '@features/FleetSegment/apis' import { setError } from '../../../domain/shared_slices/Global' -import type { FleetSegment, UpdateFleetSegment } from '../types' +import type { FleetSegment } from '../types' /** * Create a fleet segment */ export const createFleetSegment = - (segmentFields: UpdateFleetSegment, previousFleetSegments: FleetSegment[]) => + (segmentFields: FleetSegment, previousFleetSegments: FleetSegment[]) => async (dispatch): Promise => { try { if (!segmentFields?.segment) { diff --git a/frontend/src/features/FleetSegment/useCases/updateFleetSegment.ts b/frontend/src/features/FleetSegment/useCases/updateFleetSegment.ts index 34657b26e7..2faeec2516 100644 --- a/frontend/src/features/FleetSegment/useCases/updateFleetSegment.ts +++ b/frontend/src/features/FleetSegment/useCases/updateFleetSegment.ts @@ -1,8 +1,8 @@ -import { updateFleetSegmentFromAPI } from '@features/FleetSegment/apis' +import { fleetSegmentApi } from '@features/FleetSegment/apis' import { setError } from '../../../domain/shared_slices/Global' -import type { FleetSegment, UpdateFleetSegment } from '../types' +import type { FleetSegment } from '../types' /** * Update a fleet segment @@ -10,17 +10,21 @@ import type { FleetSegment, UpdateFleetSegment } from '../types' export const updateFleetSegment = ( segment: string, - year: number, - updatedFields: UpdateFleetSegment, + updatedSegment: FleetSegment, previousFleetSegments: FleetSegment[] ): ((dispatch, getState) => Promise) => async dispatch => { try { - if (!segment || !year) { - throw new Error('Erreur lors de la modification du segment de flotte') + if (!segment) { + throw new Error('Le champ segment est requis') } - const updatedFleetSegment = await updateFleetSegmentFromAPI(segment, year, updatedFields) + const updatedFleetSegment = await dispatch( + fleetSegmentApi.endpoints.updateFleetSegment.initiate({ + segment, + updatedSegment + }) + ).unwrap() return updateFleetSegments(previousFleetSegments, segment, updatedFleetSegment) } catch (error) { From ba8578e61d1ae762da98ae394f93a0ecc6e32593 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Tue, 28 Jan 2025 15:36:47 +0100 Subject: [PATCH 2/3] Add strict clause to zod object --- frontend/src/features/FleetSegment/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/FleetSegment/types.ts b/frontend/src/features/FleetSegment/types.ts index 9fef72d34d..27aed13302 100644 --- a/frontend/src/features/FleetSegment/types.ts +++ b/frontend/src/features/FleetSegment/types.ts @@ -7,7 +7,7 @@ export enum ScipSpeciesType { TUNA = 'TUNA' } -export const FleetSegmentSchema = z.object({ +export const FleetSegmentSchema = z.strictObject({ faoAreas: z.array(z.string()), gears: z.array(z.string()), impactRiskFactor: z.number().optional(), From 89f23766f715a8422ac10e5aa6988bc11eba4bad Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Tue, 28 Jan 2025 16:11:45 +0100 Subject: [PATCH 3/3] Remove year from fleet segment update --- frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts b/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts index 4443ac7c34..45b14f3a25 100644 --- a/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts +++ b/frontend/cypress/e2e/back_office/fleet_segment_table.spec.ts @@ -37,7 +37,7 @@ context('BackOffice > Fleet Segments Table', () => { cy.log('Should update the segment') // When - cy.intercept('PUT', `/bff/v1/admin/fleet_segments?year=${currentYear}&segment=ATL01`).as('updateFleetSegment') + cy.intercept('PUT', `/bff/v1/admin/fleet_segments?segment=ATL01`).as('updateFleetSegment') cy.get('[aria-rowindex="2"]').find('[title="Editer la ligne"]').click() cy.fill('Code', 'ATL0036')