diff --git a/Makefile b/Makefile index 168202b17e..f002e2c468 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ docker-env: # Local Development .PHONY: check-clean-archi ##LOCAL Check clean architecture imports -check-clean-archi: +check-clean-archi: cd backend/tools && ./check-clean-architecture.sh .PHONY: clean ##LOCAL Clean all backend assets and stop docker containers @@ -152,6 +152,11 @@ test-back: check-clean-archi test-back-watch: ./backend/scripts/test-watch.sh +.PHONY: run-back-with-monitorenv-for-cypress ##TEST ▶️ Run backend API when using Cypress connected to a local MonitorEnv app 📝 +run-back-with-monitorenv-for-cypress: run-monitorenv run-stubbed-apis + docker compose up -d --quiet-pull --wait db keycloak + cd backend && MONITORENV_URL=http://localhost:9880 MONITORFISH_OIDC_ENABLED=false MONITORFISH_SCHEDULING_ENABLED=false ./gradlew bootRun --args='--spring.profiles.active=local --spring.config.additional-location=$(INFRA_FOLDER)' + .PHONY: run-back-for-puppeteer ##TEST ▶️ Run backend API when using Puppeteer 📝 run-back-for-puppeteer: docker-env run-stubbed-apis docker compose up -d --quiet-pull --wait db @@ -172,7 +177,7 @@ run-front-for-puppeteer: restart-remote-app: cd infra/remote && docker compose pull && docker compose up -d --build app --force-recreate -.PHONY: register-pipeline-flows-prod ##RUN ▶️ Register pipeline flows in PROD +.PHONY: register-pipeline-flows-prod ##RUN ▶️ Register pipeline flows in PROD register-pipeline-flows-prod: docker pull docker.pkg.github.com/mtes-mct/monitorfish/monitorfish-pipeline:$(MONITORFISH_VERSION) && \ infra/remote/data-pipeline/register-flows-prod.sh diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/administration/Administration.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/administration/Administration.kt new file mode 100644 index 0000000000..04e66bba79 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/administration/Administration.kt @@ -0,0 +1,10 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.administration + +import kotlinx.serialization.Serializable + +@Serializable +data class Administration( + val id: Int, + val isArchived: Boolean, + val name: String, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnit.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnit.kt new file mode 100644 index 0000000000..f86ef6ed85 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnit.kt @@ -0,0 +1,14 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.control_unit + +import kotlinx.serialization.Serializable + +@Serializable +data class ControlUnit( + val id: Int, + val areaNote: String?, + val administrationId: Int, + val departmentAreaInseeCode: String?, + val isArchived: Boolean, + val name: String, + val termsNote: String?, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitContact.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitContact.kt new file mode 100644 index 0000000000..98c3c21de3 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitContact.kt @@ -0,0 +1,14 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.control_unit + +import kotlinx.serialization.Serializable + +@Serializable +data class ControlUnitContact( + val id: Int, + val controlUnitId: Int, + val email: String?, + val isEmailSubscriptionContact: Boolean, + val isSmsSubscriptionContact: Boolean, + val name: String, + val phone: String?, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitDepartmentArea.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitDepartmentArea.kt new file mode 100644 index 0000000000..86bf509e93 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitDepartmentArea.kt @@ -0,0 +1,11 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.control_unit + +import kotlinx.serialization.Serializable + +// TODO Maybe merge `districts` from MonitorFish into MonitorEnv `departmentAreas`? +@Serializable +data class ControlUnitDepartmentArea( + /** `inseeCode` is the ID. */ + val inseeCode: String, + val name: String, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitResourceType.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitResourceType.kt new file mode 100644 index 0000000000..98f2cf1a83 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/ControlUnitResourceType.kt @@ -0,0 +1,32 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.control_unit + +import kotlinx.serialization.Serializable + +@Serializable +enum class ControlUnitResourceType(val label: String) { + AIRPLANE("Avion"), + BARGE("Barge"), + CAR("Voiture"), + DRONE("Drône"), + EQUESTRIAN("Équestre"), + FAST_BOAT("Vedette"), + FRIGATE("Frégate"), + HELICOPTER("Hélicoptère"), + HYDROGRAPHIC_SHIP("Bâtiment hydrographique"), + KAYAK("Kayak"), + LIGHT_FAST_BOAT("Vedette légère"), + MINE_DIVER("Plongeur démineur"), + MOTORCYCLE("Moto"), + NET_LIFTER("Remonte-filets"), + NO_RESOURCE("Aucun moyen"), + OTHER("Autre"), + PATROL_BOAT("Patrouilleur"), + PEDESTRIAN("Piéton"), + PIROGUE("Pirogue"), + RIGID_HULL("Coque rigide"), + SEA_SCOOTER("Scooter de mer"), + SEMI_RIGID("Semi-rigide"), + SUPPORT_SHIP("Bâtiment de soutien"), + TRAINING_SHIP("Bâtiment-école"), + TUGBOAT("Remorqueur"), +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/ControlUnit.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/LegacyControlUnit.kt similarity index 58% rename from backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/ControlUnit.kt rename to backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/LegacyControlUnit.kt index 7890a7ad8c..69ba76542e 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/ControlUnit.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/control_unit/LegacyControlUnit.kt @@ -1,9 +1,10 @@ -package fr.gouv.cnsp.monitorfish.domain.entities.mission +package fr.gouv.cnsp.monitorfish.domain.entities.control_unit +import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlResource import kotlinx.serialization.Serializable @Serializable -data class ControlUnit( +data class LegacyControlUnit( val id: Int, val administration: String, val isArchived: Boolean, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/fleet_segment/FleetSegment.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/fleet_segment/FleetSegment.kt index 8dc725bf6c..4eb8481e69 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/fleet_segment/FleetSegment.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/fleet_segment/FleetSegment.kt @@ -3,7 +3,9 @@ package fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment import fr.gouv.cnsp.monitorfish.domain.entities.logbook.LogbookTripSegment data class FleetSegment( + // TODO Rename that to `code`. val segment: String, + // TODO Rename that to `name`. val segmentName: String, val dirm: List = listOf(), val gears: List, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/Mission.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/Mission.kt index 0c6166fa35..462e7d7674 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/Mission.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/Mission.kt @@ -1,5 +1,6 @@ package fr.gouv.cnsp.monitorfish.domain.entities.mission +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.env_mission_action.EnvMissionAction import java.time.ZonedDateTime @@ -25,5 +26,5 @@ data class Mission( val missionSource: MissionSource, val hasMissionOrder: Boolean? = false, val isUnderJdp: Boolean? = false, - val controlUnits: List = listOf(), + val controlUnits: List = listOf(), ) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/MissionAction.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/MissionAction.kt index ff47d870f1..7bb16cfe51 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/MissionAction.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/MissionAction.kt @@ -2,7 +2,7 @@ package fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions import com.neovisionaries.i18n.CountryCode import fr.gouv.cnsp.monitorfish.config.Patchable -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import java.time.ZonedDateTime data class MissionAction( @@ -58,7 +58,7 @@ data class MissionAction( * This field is only used by the `GetVesselControls` use-case. * /!\ Do not use it to get `controlUnits` as the field will be empty be default. */ - var controlUnits: List = listOf(), + var controlUnits: List = listOf(), val isDeleted: Boolean, val hasSomeGearsSeized: Boolean, val hasSomeSpeciesSeized: Boolean, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationFleetSegmentSubscription.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationFleetSegmentSubscription.kt new file mode 100644 index 0000000000..41cb256fb5 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationFleetSegmentSubscription.kt @@ -0,0 +1,7 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification + +data class PriorNotificationFleetSegmentSubscription( + val controlUnitId: Int, + val segmentCode: String, + val segmentName: String?, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationPortSubscription.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationPortSubscription.kt new file mode 100644 index 0000000000..b7a505c239 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationPortSubscription.kt @@ -0,0 +1,8 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification + +data class PriorNotificationPortSubscription( + val controlUnitId: Int, + val portLocode: String, + val portName: String?, + val hasSubscribedToAllPriorNotifications: Boolean, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationVesselSubscription.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationVesselSubscription.kt new file mode 100644 index 0000000000..eeb678f2ec --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/PriorNotificationVesselSubscription.kt @@ -0,0 +1,11 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification + +data class PriorNotificationVesselSubscription( + val controlUnitId: Int, + val vesselId: Int, + val vesselCallSign: String?, + val vesselCfr: String?, + val vesselExternalMarking: String?, + val vesselMmsi: String?, + val vesselName: String?, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/filters/PriorNotificationSubscribersFilter.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/filters/PriorNotificationSubscribersFilter.kt new file mode 100644 index 0000000000..5ea5fdb1c1 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/filters/PriorNotificationSubscribersFilter.kt @@ -0,0 +1,7 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.filters + +data class PriorNotificationSubscribersFilter( + val administrationId: Int? = null, + val portLocode: String? = null, + val searchQuery: String? = null, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/sorters/PriorNotificationSubscribersSortColumn.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/sorters/PriorNotificationSubscribersSortColumn.kt new file mode 100644 index 0000000000..0a5811d4d6 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/prior_notification/sorters/PriorNotificationSubscribersSortColumn.kt @@ -0,0 +1,5 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.sorters + +enum class PriorNotificationSubscribersSortColumn { + CONTROL_UNIT_NAME, +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/reporting/ReportingAndOccurrences.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/reporting/ReportingAndOccurrences.kt index 45e5e39862..390031d32b 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/reporting/ReportingAndOccurrences.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/reporting/ReportingAndOccurrences.kt @@ -1,9 +1,9 @@ package fr.gouv.cnsp.monitorfish.domain.entities.reporting -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit data class ReportingAndOccurrences( val otherOccurrencesOfSameAlert: List, val reporting: Reporting, - val controlUnit: ControlUnit?, + val controlUnit: LegacyControlUnit?, ) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/station/Station.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/station/Station.kt new file mode 100644 index 0000000000..68b6ca3a37 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/station/Station.kt @@ -0,0 +1,11 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.station + +import kotlinx.serialization.Serializable + +@Serializable +data class Station( + val id: Int, + val latitude: Double, + val longitude: Double, + val name: String, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/Vessel.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/Vessel.kt index 19afb2e90e..f3523e5a27 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/Vessel.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/Vessel.kt @@ -4,17 +4,22 @@ import com.neovisionaries.i18n.CountryCode import fr.gouv.cnsp.monitorfish.domain.FRENCH_COUNTRY_CODES import java.util.* +// TODO Remove all default values. data class Vessel( val id: Int, - /** CFR (Community Fleet Register Number). */ + // TODO Rename to either `cfr` (domain naming) or `commonFleetRegisterNumber` (ex: https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX%3A32017R0218). + /** CFR (Common Fleet Register Number). */ val internalReferenceNumber: String? = null, /** IMO (International Maritime Organization). IMO is one of multiple UVI (Unique Vessel Identifier) types. */ val imo: String? = null, /** MMSI (Maritime Mobile Service Identity). */ val mmsi: String? = null, + // TODO Rename to either `callSign` (domain naming) or `internationRadioCallSign`. /** IRCS (International Radio Call Sign). */ val ircs: String? = null, + // TODO Rename to `externalMarking` (domaim naming + correct translation, ex: https://mer.gouv.fr/sites/default/files/2022-10/EU_Vessel_List_for_Jersey_Waters_Access_sept_22.pdf). val externalReferenceNumber: String? = null, + // TODO Rename to `name`. val vesselName: String? = null, val flagState: CountryCode, val width: Double? = null, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt index 5bcb3a917a..cc18b5b2fa 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/ControlUnitRepository.kt @@ -1,7 +1,7 @@ package fr.gouv.cnsp.monitorfish.domain.repositories -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnit interface ControlUnitRepository { - fun findAll(): List + fun findAll(): List } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/LegacyControlUnitRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/LegacyControlUnitRepository.kt new file mode 100644 index 0000000000..f4287e3f53 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/LegacyControlUnitRepository.kt @@ -0,0 +1,7 @@ +package fr.gouv.cnsp.monitorfish.domain.repositories + +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit + +interface LegacyControlUnitRepository { + fun findAll(): List +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionRepository.kt index 2091a88d64..c73ef8bdd0 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionRepository.kt @@ -1,6 +1,6 @@ package fr.gouv.cnsp.monitorfish.domain.repositories -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.Mission import fr.gouv.cnsp.monitorfish.domain.exceptions.CouldNotFindException import kotlinx.coroutines.CoroutineScope @@ -11,7 +11,7 @@ interface MissionRepository { fun findControlUnitsOfMission( scope: CoroutineScope, missionId: Int, - ): Deferred> + ): Deferred> fun findAllMissions( pageNumber: Int?, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoFleetSegmentSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoFleetSegmentSubscriptionRepository.kt new file mode 100644 index 0000000000..b8b74cedbc --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoFleetSegmentSubscriptionRepository.kt @@ -0,0 +1,20 @@ +package fr.gouv.cnsp.monitorfish.domain.repositories + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription + +interface PnoFleetSegmentSubscriptionRepository { + fun deleteByControlUnitId(controlUnitId: Int) + + fun findAll(): List + + fun findByControlUnitId(controlUnitId: Int): List + + fun has( + portLocode: String, + segmentCodes: List, + ): Boolean + + fun saveAll( + priorNotificationFleetSegmentSubscriptions: List, + ): List +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoPortSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoPortSubscriptionRepository.kt index 17d8f0f9a6..70465e432d 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoPortSubscriptionRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoPortSubscriptionRepository.kt @@ -1,5 +1,17 @@ package fr.gouv.cnsp.monitorfish.domain.repositories +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription + interface PnoPortSubscriptionRepository { + fun deleteByControlUnitId(controlUnitId: Int) + + fun findAll(): List + + fun findByControlUnitId(controlUnitId: Int): List + fun has(portLocode: String): Boolean + + fun saveAll( + priorNotificationPortSubscriptions: List, + ): List } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoSegmentSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoSegmentSubscriptionRepository.kt deleted file mode 100644 index 32b0f522d6..0000000000 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoSegmentSubscriptionRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package fr.gouv.cnsp.monitorfish.domain.repositories - -interface PnoSegmentSubscriptionRepository { - fun has( - portLocode: String, - segmentCodes: List, - ): Boolean -} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoVesselSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoVesselSubscriptionRepository.kt index 7e07a00130..6a2c84f09f 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoVesselSubscriptionRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PnoVesselSubscriptionRepository.kt @@ -1,5 +1,17 @@ package fr.gouv.cnsp.monitorfish.domain.repositories +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription + interface PnoVesselSubscriptionRepository { + fun deleteByControlUnitId(controlUnitId: Int) + + fun findAll(): List + + fun findByControlUnitId(controlUnitId: Int): List + fun has(vesselId: Int): Boolean + + fun saveAll( + priorNotificationVesselSubscriptions: List, + ): List } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt index c36e849066..2ab16c62da 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllControlUnits.kt @@ -1,18 +1,14 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.control_units import fr.gouv.cnsp.monitorfish.config.UseCase -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit import fr.gouv.cnsp.monitorfish.domain.repositories.ControlUnitRepository -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnit @UseCase class GetAllControlUnits( private val controlUnitsRepository: ControlUnitRepository, ) { - private val logger: Logger = LoggerFactory.getLogger(GetAllControlUnits::class.java) - - fun execute(): List { + fun execute(): List { return controlUnitsRepository.findAll() } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllLegacyControlUnits.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllLegacyControlUnits.kt new file mode 100644 index 0000000000..d65ee9cc32 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/GetAllLegacyControlUnits.kt @@ -0,0 +1,14 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.control_units + +import fr.gouv.cnsp.monitorfish.config.UseCase +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit +import fr.gouv.cnsp.monitorfish.domain.repositories.LegacyControlUnitRepository + +@UseCase +class GetAllLegacyControlUnits( + private val legacyControlUnitsRepository: LegacyControlUnitRepository, +) { + fun execute(): List { + return legacyControlUnitsRepository.findAll() + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/dtos/FullControlUnit.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/dtos/FullControlUnit.kt new file mode 100644 index 0000000000..828c6c2ae3 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/dtos/FullControlUnit.kt @@ -0,0 +1,23 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos + +import fr.gouv.cnsp.monitorfish.domain.entities.administration.Administration +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.ControlUnitContact +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.ControlUnitDepartmentArea +import kotlinx.serialization.Serializable + +@Serializable +data class FullControlUnit( + val id: Int, + val areaNote: String?, + val administration: Administration, + val administrationId: Int, + val controlUnitContactIds: List, + val controlUnitContacts: List, + val controlUnitResourceIds: List, + val controlUnitResources: List, + val departmentArea: ControlUnitDepartmentArea?, + val departmentAreaInseeCode: String?, + val isArchived: Boolean, + val name: String, + val termsNote: String?, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/dtos/FullControlUnitResource.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/dtos/FullControlUnitResource.kt new file mode 100644 index 0000000000..b6946e0755 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/control_units/dtos/FullControlUnitResource.kt @@ -0,0 +1,20 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos + +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.ControlUnitResourceType +import fr.gouv.cnsp.monitorfish.domain.entities.station.Station +import kotlinx.serialization.Serializable + +@Serializable +data class FullControlUnitResource( + val id: Int, + val controlUnit: ControlUnit, + val controlUnitId: Int, + val isArchived: Boolean, + val name: String, + val note: String?, + val photo: ByteArray?, + val station: Station, + val stationId: Int, + val type: ControlUnitResourceType, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt index d35548647c..aa941e6740 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt @@ -75,12 +75,9 @@ class GetActivityReports( val controlMission = missions.firstOrNull { mission -> mission.id == control.missionId } // All AECP reports are excluded from the response // see: https://github.com/MTES-MCT/monitorfish/issues/3194 - return@filter !( - controlMission?.controlUnits?.any { - controlUnit -> - controlUnit.administration == "AECP" - } ?: false - ) + return@filter controlMission?.controlUnits?.any { controlUnit -> + controlUnit.administration == "AECP" + } != true } logger.info("Found ${filteredControls.size} controls to report.") diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt index 53df863986..4cd8dec0bc 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt @@ -1,6 +1,6 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions.dtos -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionAction import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.ActivityCode import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel @@ -12,6 +12,6 @@ data class ActivityReport( val activityCode: ActivityCode, // The `districtCode` and `internalReferenceNumber` concatenation val vesselNationalIdentifier: String, - val controlUnits: List, + val controlUnits: List, val vessel: Vessel, ) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/ComputeManualPriorNotification.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/ComputeManualPriorNotification.kt index a8dfa2a208..6cb8b4c850 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/ComputeManualPriorNotification.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/ComputeManualPriorNotification.kt @@ -4,8 +4,8 @@ import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.logbook.LogbookFishingCatch import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.ManualPriorNotificationComputedValues import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotification +import fr.gouv.cnsp.monitorfish.domain.repositories.PnoFleetSegmentSubscriptionRepository import fr.gouv.cnsp.monitorfish.domain.repositories.PnoPortSubscriptionRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.PnoSegmentSubscriptionRepository import fr.gouv.cnsp.monitorfish.domain.repositories.PnoVesselSubscriptionRepository import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.ComputeFleetSegments @@ -13,7 +13,7 @@ import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.ComputeFleetSegme @UseCase class ComputeManualPriorNotification( private val pnoPortSubscriptionRepository: PnoPortSubscriptionRepository, - private val pnoSegmentSubscriptionRepository: PnoSegmentSubscriptionRepository, + private val pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository, private val pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository, private val vesselRepository: VesselRepository, private val computeFleetSegments: ComputeFleetSegments, @@ -48,7 +48,7 @@ class ComputeManualPriorNotification( val isPartOfControlUnitSubscriptions = pnoPortSubscriptionRepository.has(portLocode) || pnoVesselSubscriptionRepository.has(vesselId) || - pnoSegmentSubscriptionRepository.has(portLocode, tripSegments.map { it.segment }) + pnoFleetSegmentSubscriptionRepository.has(portLocode, tripSegments.map { it.segment }) val nextState = PriorNotification.getNextState(isInVerificationScope, isPartOfControlUnitSubscriptions) return ManualPriorNotificationComputedValues( diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt index 5877037e45..db0c41ca6f 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt @@ -16,8 +16,8 @@ import java.time.ZonedDateTime class CreateOrUpdateManualPriorNotification( private val gearRepository: GearRepository, private val manualPriorNotificationRepository: ManualPriorNotificationRepository, + private val pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository, private val pnoPortSubscriptionRepository: PnoPortSubscriptionRepository, - private val pnoSegmentSubscriptionRepository: PnoSegmentSubscriptionRepository, private val pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository, private val portRepository: PortRepository, private val priorNotificationPdfDocumentRepository: PriorNotificationPdfDocumentRepository, @@ -69,7 +69,7 @@ class CreateOrUpdateManualPriorNotification( val isPartOfControlUnitSubscriptions = pnoPortSubscriptionRepository.has(portLocode) || pnoVesselSubscriptionRepository.has(vesselId) || - pnoSegmentSubscriptionRepository.has(portLocode, computedValues.tripSegments.map { it.segment }) + pnoFleetSegmentSubscriptionRepository.has(portLocode, computedValues.tripSegments.map { it.segment }) val fishingCatchesWithFaoArea = globalFaoArea?.let { fishingCatches.map { it.copy(faoZone = globalFaoArea) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscriber.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscriber.kt new file mode 100644 index 0000000000..d5c8ed7b1e --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscriber.kt @@ -0,0 +1,43 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification + +import fr.gouv.cnsp.monitorfish.config.UseCase +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageException +import fr.gouv.cnsp.monitorfish.domain.repositories.* +import fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.dtos.PriorNotificationSubscriber + +@UseCase +class GetPriorNotificationSubscriber( + private val controlUnitRepository: ControlUnitRepository, + private val fleetSegmentRepository: FleetSegmentRepository, + private val pnoPortSubscriptionRepository: PnoPortSubscriptionRepository, + private val pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository, + private val pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository, + private val portRepository: PortRepository, + private val vesselRepository: VesselRepository, +) { + fun execute(id: Int): PriorNotificationSubscriber { + val allFleetSegments = fleetSegmentRepository.findAll() + val allPorts = portRepository.findAll() + val allVessels = vesselRepository.findAll() + + val controlUnit = controlUnitRepository.findAll().find { it.id == id } + if (controlUnit == null) { + throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND) + } + + val fleetSegmentSubscriptions = pnoFleetSegmentSubscriptionRepository.findByControlUnitId(id) + val portSubscriptions = pnoPortSubscriptionRepository.findByControlUnitId(id) + val vesselSubscriptions = pnoVesselSubscriptionRepository.findByControlUnitId(id) + + return PriorNotificationSubscriber.create( + controlUnit, + fleetSegmentSubscriptions, + portSubscriptions, + vesselSubscriptions, + allFleetSegments, + allPorts, + allVessels, + ) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscribers.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscribers.kt new file mode 100644 index 0000000000..0f8ab09b2c --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscribers.kt @@ -0,0 +1,132 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification + +import fr.gouv.cnsp.monitorfish.config.UseCase +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.filters.PriorNotificationSubscribersFilter +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.sorters.PriorNotificationSubscribersSortColumn +import fr.gouv.cnsp.monitorfish.domain.repositories.* +import fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.dtos.PriorNotificationSubscriber +import fr.gouv.cnsp.monitorfish.utils.StringUtils +import org.springframework.data.domain.Sort + +@UseCase +class GetPriorNotificationSubscribers( + private val controlUnitRepository: ControlUnitRepository, + private val fleetSegmentRepository: FleetSegmentRepository, + private val pnoPortSubscriptionRepository: PnoPortSubscriptionRepository, + private val pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository, + private val pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository, + private val portRepository: PortRepository, + private val vesselRepository: VesselRepository, +) { + fun execute( + filter: PriorNotificationSubscribersFilter, + sortColumn: PriorNotificationSubscribersSortColumn, + sortDirection: Sort.Direction, + ): List { + val priorNotificationSubscribers = getPriorNotificationSubscribers() + val filteredPriorNotificationSubscribers = + filterPriorNotificationSubscribers(subscribers = priorNotificationSubscribers, filter = filter) + + return sortPriorNotificationSubscribers( + subscribers = filteredPriorNotificationSubscribers, + sortColumn = sortColumn, + sortDirection = sortDirection, + ) + } + + private fun getPriorNotificationSubscribers(): List { + val allFleetSegments = fleetSegmentRepository.findAll() + val allPorts = portRepository.findAll() + val allVessels = vesselRepository.findAll() + + val allControlUnits = controlUnitRepository.findAll() + val allFleetSegmentSubscriptions = pnoFleetSegmentSubscriptionRepository.findAll() + val allPortSubscriptions = pnoPortSubscriptionRepository.findAll() + val allVesselSubscriptions = pnoVesselSubscriptionRepository.findAll() + + return allControlUnits.map { controlUnit -> + val fleetSegmentSubscriptions = + allFleetSegmentSubscriptions.filter { segmentSubscription -> + segmentSubscription.controlUnitId == controlUnit.id + } + val portSubscriptions = + allPortSubscriptions.filter { portSubscription -> + portSubscription.controlUnitId == controlUnit.id + } + val vesselSubscriptions = + allVesselSubscriptions.filter { vesselSubscription -> + vesselSubscription.controlUnitId == controlUnit.id + } + + return@map PriorNotificationSubscriber.create( + controlUnit, + fleetSegmentSubscriptions, + portSubscriptions, + vesselSubscriptions, + allFleetSegments, + allPorts, + allVessels, + ) + } + } + + private fun filterPriorNotificationSubscribers( + subscribers: List, + filter: PriorNotificationSubscribersFilter, + ): List { + return subscribers.filter { subscriber -> + val administrationIdMatches = + filter.administrationId?.let { + subscriber.controlUnit.administration.id == it + } != false + + val portLocodeMatches = + filter.portLocode?.let { + subscriber.portSubscriptions.any { portSubscription -> portSubscription.portLocode == it } + } != false + + val searchQueryMatches = + filter.searchQuery?.let { query -> + val normalizedQuery = StringUtils.removeAccents(query).lowercase() + + val controlUnitNameMatches = + StringUtils.removeAccents(subscriber.controlUnit.name) + .lowercase() + .contains(normalizedQuery) + + val administrationNameMatches = + StringUtils.removeAccents(subscriber.controlUnit.administration.name) + .lowercase() + .contains(normalizedQuery) + + val portNameMatches = + subscriber.portSubscriptions.any { portSubscription -> + portSubscription.portName + ?.let(StringUtils::removeAccents) + ?.lowercase() + ?.contains(normalizedQuery) == true + } + + controlUnitNameMatches || administrationNameMatches || portNameMatches + } != false + + administrationIdMatches && portLocodeMatches && searchQueryMatches + } + } + + private fun sortPriorNotificationSubscribers( + subscribers: List, + sortColumn: PriorNotificationSubscribersSortColumn, + sortDirection: Sort.Direction, + ): List { + val comparator = + when (sortColumn) { + PriorNotificationSubscribersSortColumn.CONTROL_UNIT_NAME -> + compareBy { it.controlUnit.name } + } + + return subscribers.sortedWith( + if (sortDirection == Sort.Direction.ASC) comparator else comparator.reversed(), + ) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationSubscriber.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationSubscriber.kt new file mode 100644 index 0000000000..d64114b73a --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationSubscriber.kt @@ -0,0 +1,60 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification + +import fr.gouv.cnsp.monitorfish.config.UseCase +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageException +import fr.gouv.cnsp.monitorfish.domain.repositories.ControlUnitRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.FleetSegmentRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.PnoFleetSegmentSubscriptionRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.PnoPortSubscriptionRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.PnoVesselSubscriptionRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.PortRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository +import fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.dtos.PriorNotificationSubscriber + +@UseCase +class UpdatePriorNotificationSubscriber( + private val controlUnitRepository: ControlUnitRepository, + private val fleetSegmentRepository: FleetSegmentRepository, + private val pnoPortSubscriptionRepository: PnoPortSubscriptionRepository, + private val pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository, + private val pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository, + private val portRepository: PortRepository, + private val vesselRepository: VesselRepository, +) { + fun execute( + controlUnitId: Int, + fleetSegmentSubscriptions: List, + portSubscriptions: List, + vesselSubscriptions: List, + ): PriorNotificationSubscriber { + val allFleetSegments = fleetSegmentRepository.findAll() + val allPorts = portRepository.findAll() + val allVessels = vesselRepository.findAll() + + val controlUnit = controlUnitRepository.findAll().find { it.id == controlUnitId } + if (controlUnit == null) { + throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND) + } + + pnoPortSubscriptionRepository.deleteByControlUnitId(controlUnitId) + pnoFleetSegmentSubscriptionRepository.deleteByControlUnitId(controlUnitId) + pnoVesselSubscriptionRepository.deleteByControlUnitId(controlUnitId) + pnoFleetSegmentSubscriptionRepository.saveAll(fleetSegmentSubscriptions) + pnoPortSubscriptionRepository.saveAll(portSubscriptions) + pnoVesselSubscriptionRepository.saveAll(vesselSubscriptions) + + return PriorNotificationSubscriber.create( + controlUnit, + fleetSegmentSubscriptions, + portSubscriptions, + vesselSubscriptions, + allFleetSegments, + allPorts, + allVessels, + ) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/dtos/PriorNotificationSubscriber.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/dtos/PriorNotificationSubscriber.kt new file mode 100644 index 0000000000..9b959fb125 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/dtos/PriorNotificationSubscriber.kt @@ -0,0 +1,62 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.dtos + +import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment +import fr.gouv.cnsp.monitorfish.domain.entities.port.Port +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnit + +data class PriorNotificationSubscriber( + val controlUnit: FullControlUnit, + val fleetSegmentSubscriptions: List, + val portSubscriptions: List, + val vesselSubscriptions: List, +) { + companion object { + fun create( + controlUnit: FullControlUnit, + fleetSegmentSubscriptions: List, + portSubscriptions: List, + vesselSubscriptions: List, + allFleetSegments: List, + allPorts: List, + allVessels: List, + ): PriorNotificationSubscriber { + portSubscriptions.any { portSubscription -> portSubscription.hasSubscribedToAllPriorNotifications } + + val enrichedFleetSegmentSubscriptions = + fleetSegmentSubscriptions.map { fleetSegmentSubscription -> + val fleetSegment = allFleetSegments.find { it.segment == fleetSegmentSubscription.segmentCode } + + return@map fleetSegmentSubscription.copy(segmentName = fleetSegment?.segmentName) + } + val enrichedPortSubscriptions = + portSubscriptions.map { portSubscription -> + val port = allPorts.find { it.locode == portSubscription.portLocode } + + return@map portSubscription.copy(portName = port?.name) + } + val enrichedVesselSubscriptions = + vesselSubscriptions.map { vesselSubscription -> + val vessel = allVessels.find { it.id == vesselSubscription.vesselId } + + return@map vesselSubscription.copy( + vesselCallSign = vessel?.ircs, + vesselCfr = vessel?.internalReferenceNumber, + vesselExternalMarking = vessel?.externalReferenceNumber, + vesselMmsi = vessel?.mmsi, + vesselName = vessel?.vesselName, + ) + } + + return PriorNotificationSubscriber( + controlUnit, + fleetSegmentSubscriptions = enrichedFleetSegmentSubscriptions, + portSubscriptions = enrichedPortSubscriptions, + vesselSubscriptions = enrichedVesselSubscriptions, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/AddReporting.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/AddReporting.kt index 0c3a84d12a..28f6088556 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/AddReporting.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/AddReporting.kt @@ -1,13 +1,13 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.reporting import fr.gouv.cnsp.monitorfish.config.UseCase -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.InfractionSuspicion import fr.gouv.cnsp.monitorfish.domain.entities.reporting.InfractionSuspicionOrObservationType import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Reporting import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -15,11 +15,11 @@ import org.slf4j.LoggerFactory class AddReporting( private val reportingRepository: ReportingRepository, private val getInfractionSuspicionWithDMLAndSeaFront: GetInfractionSuspicionWithDMLAndSeaFront, - private val getAllControlUnits: GetAllControlUnits, + private val getAllLegacyControlUnits: GetAllLegacyControlUnits, ) { private val logger: Logger = LoggerFactory.getLogger(AddReporting::class.java) - fun execute(newReporting: Reporting): Pair { + fun execute(newReporting: Reporting): Pair { logger.info( "Adding reporting for vessel ${newReporting.internalReferenceNumber}/${newReporting.ircs}/${newReporting.externalReferenceNumber}", ) @@ -28,7 +28,7 @@ class AddReporting( "The reporting type must be OBSERVATION or INFRACTION_SUSPICION" } - val controlUnits = getAllControlUnits.execute() + val controlUnits = getAllLegacyControlUnits.execute() newReporting.value as InfractionSuspicionOrObservationType newReporting.value.checkReportingActorAndFieldsRequirements() diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetAllCurrentReportings.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetAllCurrentReportings.kt index 83b43b6a53..a56b8c68f2 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetAllCurrentReportings.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetAllCurrentReportings.kt @@ -1,7 +1,7 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.reporting import fr.gouv.cnsp.monitorfish.config.UseCase -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.InfractionSuspicionOrObservationType import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Reporting import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType @@ -9,7 +9,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.reporting.filters.ReportingFilte import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -17,11 +17,11 @@ import org.slf4j.LoggerFactory class GetAllCurrentReportings( private val reportingRepository: ReportingRepository, private val vesselRepository: VesselRepository, - private val getAllControlUnits: GetAllControlUnits, + private val getAllLegacyControlUnits: GetAllLegacyControlUnits, ) { private val logger: Logger = LoggerFactory.getLogger(GetAllCurrentReportings::class.java) - fun execute(): List> { + fun execute(): List> { val filter = ReportingFilter( isArchived = false, @@ -30,7 +30,7 @@ class GetAllCurrentReportings( ) val currentReportings = reportingRepository.findAll(filter) - val controlUnits = getAllControlUnits.execute() + val controlUnits = getAllLegacyControlUnits.execute() val currentReportingsWithCharterInfo = currentReportings.map { reporting -> diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportings.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportings.kt index ff767fb1d0..2604fad88a 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportings.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportings.kt @@ -3,13 +3,13 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.reporting import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.alerts.type.AlertType import fr.gouv.cnsp.monitorfish.domain.entities.alerts.type.AlertTypeMapping -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.* import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import fr.gouv.cnsp.monitorfish.domain.exceptions.NatinfCodeNotFoundException import fr.gouv.cnsp.monitorfish.domain.repositories.InfractionRepository import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import org.slf4j.LoggerFactory import java.time.ZonedDateTime import kotlin.time.measureTimedValue @@ -18,7 +18,7 @@ import kotlin.time.measureTimedValue class GetVesselReportings( private val reportingRepository: ReportingRepository, private val infractionRepository: InfractionRepository, - private val getAllControlUnits: GetAllControlUnits, + private val getAllLegacyControlUnits: GetAllLegacyControlUnits, ) { private val logger = LoggerFactory.getLogger(GetVesselReportings::class.java) @@ -30,7 +30,7 @@ class GetVesselReportings( vesselIdentifier: VesselIdentifier?, fromDate: ZonedDateTime, ): VesselReportings { - val (controlUnits, controlUnitsTimeTaken) = measureTimedValue { getAllControlUnits.execute() } + val (controlUnits, controlUnitsTimeTaken) = measureTimedValue { getAllLegacyControlUnits.execute() } logger.info("TIME_RECORD - 'getAllControlUnits' took $controlUnitsTimeTaken") val (reportings, reportingsTimeTaken) = @@ -152,7 +152,7 @@ class GetVesselReportings( private fun enrichWithInfractionAndControlUnit( reportingAndOccurrences: ReportingAndOccurrences, - controlUnits: List, + controlUnits: List, ): ReportingAndOccurrences { val updatedInfraction = reportingAndOccurrences.reporting.value.natinfCode?.let { natinfCode -> diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/UpdateReporting.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/UpdateReporting.kt index 4e01be94da..97775748d2 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/UpdateReporting.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/UpdateReporting.kt @@ -1,10 +1,10 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.reporting import fr.gouv.cnsp.monitorfish.config.UseCase -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.* import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -12,16 +12,16 @@ import org.slf4j.LoggerFactory class UpdateReporting( private val reportingRepository: ReportingRepository, private val getInfractionSuspicionWithDMLAndSeaFront: GetInfractionSuspicionWithDMLAndSeaFront, - private val getAllControlUnits: GetAllControlUnits, + private val getAllLegacyControlUnits: GetAllLegacyControlUnits, ) { private val logger: Logger = LoggerFactory.getLogger(UpdateReporting::class.java) fun execute( reportingId: Int, updatedInfractionSuspicionOrObservation: UpdatedInfractionSuspicionOrObservation, - ): Pair { + ): Pair { val currentReporting = reportingRepository.findById(reportingId) - val controlUnits = getAllControlUnits.execute() + val controlUnits = getAllLegacyControlUnits.execute() logger.info("Updating reporting id $reportingId for vessel id ${currentReporting.vesselId}") require(currentReporting.type != ReportingType.ALERT) { @@ -43,6 +43,7 @@ class UpdateReporting( Pair(updatedReporting, controlUnit) } + ReportingType.INFRACTION_SUSPICION -> { currentReporting.value as InfractionSuspicionOrObservationType @@ -59,6 +60,7 @@ class UpdateReporting( Pair(updatedReporting, controlUnit) } + else -> throw IllegalArgumentException( "The new reporting type must be an INFRACTION_SUSPICION or an OBSERVATION", ) @@ -67,8 +69,8 @@ class UpdateReporting( fun getControlUnit( reporting: Reporting, - controlUnits: List, - ): ControlUnit? { + controlUnits: List, + ): LegacyControlUnit? { val controlUnitId = (reporting.value as InfractionSuspicionOrObservationType).controlUnitId return controlUnits.find { it.id == controlUnitId } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationSubscriberController.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationSubscriberController.kt new file mode 100644 index 0000000000..bfaa8747d3 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationSubscriberController.kt @@ -0,0 +1,95 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.bff + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.filters.PriorNotificationSubscribersFilter +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.sorters.PriorNotificationSubscribersSortColumn +import fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.* +import fr.gouv.cnsp.monitorfish.infrastructure.api.input.PriorNotificationSubscriberDataInput +import fr.gouv.cnsp.monitorfish.infrastructure.api.outputs.* +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.websocket.server.PathParam +import org.springframework.data.domain.Sort +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/bff/v1/prior_notification_subscribers") +@Tag(name = "Prior notifications endpoints") +class PriorNotificationSubscriberController( + private val getPriorNotificationSubscriber: GetPriorNotificationSubscriber, + private val getPriorNotificationSubscribers: GetPriorNotificationSubscribers, + private val updatePriorNotificationSubscriber: UpdatePriorNotificationSubscriber, +) { + @GetMapping("") + @Operation(summary = "Get all prior notification subscribers") + fun getAll( + @Parameter(description = "Administration ID.") + @RequestParam(name = "administrationId") + administrationId: Int? = null, + @Parameter(description = "Port locode.") + @RequestParam(name = "portLocode") + portLocode: String? = null, + @Parameter(description = "Search query (vessel name).") + @RequestParam(name = "searchQuery") + searchQuery: String? = null, + @Parameter(description = "Sort column.") + @RequestParam(name = "sortColumn") + sortColumn: PriorNotificationSubscribersSortColumn, + @Parameter(description = "Sort order.") + @RequestParam(name = "sortDirection") + sortDirection: Sort.Direction, + ): List { + val filter = + PriorNotificationSubscribersFilter( + administrationId = administrationId, + portLocode = portLocode, + searchQuery = searchQuery, + ) + + val priorNotificationSubscribers = getPriorNotificationSubscribers.execute(filter, sortColumn, sortDirection) + + return priorNotificationSubscribers.map { + PriorNotificationSubscriberDataOutput.fromPriorNotificationSubscriber(it) + } + } + + @GetMapping("/{controlUnitId}") + @Operation(summary = "Get a prior notification subscriber by its `controlUnitId`") + fun getOne( + @PathParam("Control unit ID") + @PathVariable(name = "controlUnitId") + controlUnitId: Int, + ): PriorNotificationSubscriberDataOutput { + val priorNotificationsSubscriber = getPriorNotificationSubscriber.execute(controlUnitId) + + return PriorNotificationSubscriberDataOutput.fromPriorNotificationSubscriber( + priorNotificationsSubscriber, + ) + } + + @PutMapping("/{controlUnitId}") + @Operation(summary = "Update a prior notification subscriber by its `controlUnitId`") + fun updateOne( + @PathParam("Control unit ID") + @PathVariable(name = "controlUnitId") + controlUnitId: Int, + @RequestBody + priorNotificationSubscriberDataInput: PriorNotificationSubscriberDataInput, + ): PriorNotificationSubscriberDataOutput { + val (fleetSegmentSubscriptions, portSubscriptions, vesselSubscriptions) = + priorNotificationSubscriberDataInput.toSubscriptions() + + println("controlUnitId: $controlUnitId") + val updatedPriorNotificationSubscriber = + updatePriorNotificationSubscriber.execute( + controlUnitId, + fleetSegmentSubscriptions, + portSubscriptions, + vesselSubscriptions, + ) + + return PriorNotificationSubscriberDataOutput.fromPriorNotificationSubscriber( + updatedPriorNotificationSubscriber, + ) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/input/PriorNotificationSubscriberDataInput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/input/PriorNotificationSubscriberDataInput.kt new file mode 100644 index 0000000000..44d415e79a --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/input/PriorNotificationSubscriberDataInput.kt @@ -0,0 +1,44 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.input + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription + +data class PriorNotificationSubscriberDataInput( + val controlUnitId: Int, + val fleetSegmentCodes: List, + val portLocodes: List, + val portLocodesWithFullSubscription: List, + val vesselIds: List, +) { + fun toSubscriptions(): Triple, List, List> { + val fleetSegmentSubscriptions = + fleetSegmentCodes.map { segmentCode -> + PriorNotificationFleetSegmentSubscription(controlUnitId, segmentCode, null) + } + val portSubscriptions = + portLocodes.map { portLocode -> + val hasSubscribedToAllPriorNotifications = portLocodesWithFullSubscription.contains(portLocode) + + PriorNotificationPortSubscription(controlUnitId, portLocode, null, hasSubscribedToAllPriorNotifications) + } + val vesselSubscriptions = + vesselIds.map { vesselId -> + PriorNotificationVesselSubscription( + controlUnitId, + vesselId, + vesselCallSign = null, + vesselCfr = null, + vesselExternalMarking = null, + vesselMmsi = null, + vesselName = null, + ) + } + + return Triple( + fleetSegmentSubscriptions, + portSubscriptions, + vesselSubscriptions, + ) + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt index 64c9e3c369..4910e09be8 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt @@ -1,6 +1,6 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.ActivityCode import fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions.dtos.ActivityReport @@ -10,7 +10,7 @@ data class ActivityReportDataOutput( val faoArea: String?, val segment: String?, val vesselNationalIdentifier: String, - val controlUnits: List, + val controlUnits: List, val vessel: VesselDataOutput, ) { companion object { diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/AdministrationDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/AdministrationDataOutput.kt new file mode 100644 index 0000000000..30d4685d40 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/AdministrationDataOutput.kt @@ -0,0 +1,17 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs + +import fr.gouv.cnsp.monitorfish.domain.entities.administration.Administration + +data class AdministrationDataOutput( + val id: Int, + val name: String, +) { + companion object { + fun fromAdministration(administration: Administration): AdministrationDataOutput { + return AdministrationDataOutput( + id = administration.id, + name = administration.name, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ControlUnitDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ControlUnitDataOutput.kt new file mode 100644 index 0000000000..e7094f6d4f --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ControlUnitDataOutput.kt @@ -0,0 +1,21 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs + +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnit + +data class ControlUnitDataOutput( + val id: Int, + val administration: AdministrationDataOutput, + val name: String, +) { + companion object { + fun fromFullControlUnit(fullControlUnit: FullControlUnit): ControlUnitDataOutput { + val administration = AdministrationDataOutput.fromAdministration(fullControlUnit.administration) + + return ControlUnitDataOutput( + id = fullControlUnit.id, + administration, + name = fullControlUnit.name, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/InfractionSuspicionDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/InfractionSuspicionDataOutput.kt index 10eff5720d..4c691c8b30 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/InfractionSuspicionDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/InfractionSuspicionDataOutput.kt @@ -1,13 +1,13 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.InfractionSuspicion import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingActor data class InfractionSuspicionDataOutput( val reportingActor: ReportingActor, val controlUnitId: Int? = null, - val controlUnit: ControlUnit? = null, + val controlUnit: LegacyControlUnit? = null, val authorTrigram: String, val authorContact: String? = null, val title: String, @@ -19,7 +19,7 @@ data class InfractionSuspicionDataOutput( companion object { fun fromInfractionSuspicion( infractionSuspicion: InfractionSuspicion, - controlUnit: ControlUnit? = null, + controlUnit: LegacyControlUnit? = null, ): InfractionSuspicionDataOutput { return InfractionSuspicionDataOutput( reportingActor = infractionSuspicion.reportingActor, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionActionDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionActionDataOutput.kt index b99a16dfa9..5cc5d21f9f 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionActionDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionActionDataOutput.kt @@ -1,7 +1,7 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs import com.neovisionaries.i18n.CountryCode -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.* import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.Completion import java.time.ZonedDateTime @@ -49,7 +49,7 @@ data class MissionActionDataOutput( val otherComments: String? = null, val gearOnboard: List = listOf(), val speciesOnboard: List = listOf(), - val controlUnits: List = listOf(), + val controlUnits: List = listOf(), val userTrigram: String, val vesselTargeted: ControlCheck? = null, val hasSomeGearsSeized: Boolean, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionWithActionsDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionWithActionsDataOutput.kt index d80a020641..8d9432a496 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionWithActionsDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/MissionWithActionsDataOutput.kt @@ -1,5 +1,6 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.* import java.time.ZonedDateTime @@ -25,7 +26,7 @@ data class MissionWithActionsDataOutput( val missionSource: MissionSource, val hasMissionOrder: Boolean? = false, val isUnderJdp: Boolean? = false, - val controlUnits: List = listOf(), + val controlUnits: List = listOf(), val actions: List, ) { companion object { diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ObservationDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ObservationDataOutput.kt index e8e6049fb7..6b12dc5db3 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ObservationDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ObservationDataOutput.kt @@ -1,13 +1,13 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Observation import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingActor class ObservationDataOutput( val reportingActor: ReportingActor, val controlUnitId: Int? = null, - val controlUnit: ControlUnit? = null, + val controlUnit: LegacyControlUnit? = null, val authorTrigram: String, val authorContact: String? = null, val title: String, @@ -16,7 +16,7 @@ class ObservationDataOutput( companion object { fun fromObservation( observation: Observation, - controlUnit: ControlUnit? = null, + controlUnit: LegacyControlUnit? = null, ): ObservationDataOutput { return ObservationDataOutput( reportingActor = observation.reportingActor, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PortDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PortDataOutput.kt index 1c543cd9c6..bca6957e74 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PortDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PortDataOutput.kt @@ -3,8 +3,8 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs import fr.gouv.cnsp.monitorfish.domain.entities.port.Port data class PortDataOutput( - val locode: String?, - val name: String?, + val locode: String, + val name: String, val latitude: Double?, val longitude: Double?, val region: String?, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationFleetSegmentSubscriptionDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationFleetSegmentSubscriptionDataOutput.kt new file mode 100644 index 0000000000..8405741707 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationFleetSegmentSubscriptionDataOutput.kt @@ -0,0 +1,21 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription + +data class PriorNotificationFleetSegmentSubscriptionDataOutput( + val controlUnitId: Int, + val segmentCode: String, + val segmentName: String?, +) { + companion object { + fun fromPriorNotificationSegmentSubscription( + priorNotificationFleetSegmentSubscription: PriorNotificationFleetSegmentSubscription, + ): PriorNotificationFleetSegmentSubscriptionDataOutput { + return PriorNotificationFleetSegmentSubscriptionDataOutput( + controlUnitId = priorNotificationFleetSegmentSubscription.controlUnitId, + segmentCode = priorNotificationFleetSegmentSubscription.segmentCode, + segmentName = priorNotificationFleetSegmentSubscription.segmentName, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationPortSubscriptionDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationPortSubscriptionDataOutput.kt new file mode 100644 index 0000000000..ec0050f226 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationPortSubscriptionDataOutput.kt @@ -0,0 +1,23 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription + +data class PriorNotificationPortSubscriptionDataOutput( + val controlUnitId: Int, + val hasSubscribedToAllPriorNotifications: Boolean, + val portLocode: String, + val portName: String?, +) { + companion object { + fun fromPriorNotificationPortSubscription( + priorNotificationPortSubscription: PriorNotificationPortSubscription, + ): PriorNotificationPortSubscriptionDataOutput { + return PriorNotificationPortSubscriptionDataOutput( + controlUnitId = priorNotificationPortSubscription.controlUnitId, + hasSubscribedToAllPriorNotifications = priorNotificationPortSubscription.hasSubscribedToAllPriorNotifications, + portLocode = priorNotificationPortSubscription.portLocode, + portName = priorNotificationPortSubscription.portName, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationSubscriberDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationSubscriberDataOutput.kt new file mode 100644 index 0000000000..3475a888b3 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationSubscriberDataOutput.kt @@ -0,0 +1,37 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs + +import fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.dtos.PriorNotificationSubscriber + +data class PriorNotificationSubscriberDataOutput( + val controlUnit: ControlUnitDataOutput, + val fleetSegmentSubscriptions: List, + val portSubscriptions: List, + val vesselSubscriptions: List, +) { + companion object { + fun fromPriorNotificationSubscriber( + priorNotificationSubscriber: PriorNotificationSubscriber, + ): PriorNotificationSubscriberDataOutput { + val controlUnit = ControlUnitDataOutput.fromFullControlUnit(priorNotificationSubscriber.controlUnit) + val fleetSegmentSubscriptions = + priorNotificationSubscriber.fleetSegmentSubscriptions.map { + PriorNotificationFleetSegmentSubscriptionDataOutput.fromPriorNotificationSegmentSubscription(it) + } + val portSubscriptions = + priorNotificationSubscriber.portSubscriptions.map { + PriorNotificationPortSubscriptionDataOutput.fromPriorNotificationPortSubscription(it) + } + val vesselSubscriptions = + priorNotificationSubscriber.vesselSubscriptions.map { + PriorNotificationVesselSubscriptionDataOutput.fromPriorNotificationVesselSubscription(it) + } + + return PriorNotificationSubscriberDataOutput( + controlUnit, + fleetSegmentSubscriptions, + portSubscriptions, + vesselSubscriptions, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationVesselSubscriptionDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationVesselSubscriptionDataOutput.kt new file mode 100644 index 0000000000..25bbc9b14f --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/PriorNotificationVesselSubscriptionDataOutput.kt @@ -0,0 +1,29 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription + +data class PriorNotificationVesselSubscriptionDataOutput( + val controlUnitId: Int, + val vesselId: Int, + val vesselCallSign: String?, + val vesselCfr: String?, + val vesselExternalMarking: String?, + val vesselMmsi: String?, + val vesselName: String?, +) { + companion object { + fun fromPriorNotificationVesselSubscription( + priorNotificationFleetVesselSubscription: PriorNotificationVesselSubscription, + ): PriorNotificationVesselSubscriptionDataOutput { + return PriorNotificationVesselSubscriptionDataOutput( + controlUnitId = priorNotificationFleetVesselSubscription.controlUnitId, + vesselId = priorNotificationFleetVesselSubscription.vesselId, + vesselCallSign = priorNotificationFleetVesselSubscription.vesselCallSign, + vesselCfr = priorNotificationFleetVesselSubscription.vesselCfr, + vesselExternalMarking = priorNotificationFleetVesselSubscription.vesselExternalMarking, + vesselMmsi = priorNotificationFleetVesselSubscription.vesselMmsi, + vesselName = priorNotificationFleetVesselSubscription.vesselName, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ReportingDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ReportingDataOutput.kt index 61563581e3..c2f1abd6f3 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ReportingDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ReportingDataOutput.kt @@ -2,7 +2,7 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs import com.neovisionaries.i18n.CountryCode import fr.gouv.cnsp.monitorfish.domain.entities.alerts.type.AlertType -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.InfractionSuspicion import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Observation import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Reporting @@ -31,7 +31,7 @@ class ReportingDataOutput( companion object { fun fromReporting( reporting: Reporting, - controlUnit: ControlUnit?, + controlUnit: LegacyControlUnit?, ): ReportingDataOutput { val value = when (reporting.value) { diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt index 7543a3c463..f2ba7e253c 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt @@ -19,6 +19,7 @@ class CaffeineConfiguration { // Control Units val controlUnits = "control_units" + val legacyControlUnits = "legacy_control_units" // FAO Areas val faoAreas = "fao_areas" @@ -95,6 +96,7 @@ class CaffeineConfiguration { // Control Units val controlUnitsCache = buildMinutesCache(controlUnits, ticker, oneDay) + val legacyControlUnitsCache = buildMinutesCache(legacyControlUnits, ticker, oneDay) // FAO Areas val faoAreasCache = buildMinutesCache(faoAreas, ticker, oneWeek) @@ -188,6 +190,7 @@ class CaffeineConfiguration { gearsCache, infractionCache, infractionsCache, + legacyControlUnitsCache, logbookCache, logbookRawMessageCache, missionControlUnitsCache, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoFleetSegmentSubscriptionEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoFleetSegmentSubscriptionEntity.kt new file mode 100644 index 0000000000..fbc59dc199 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoFleetSegmentSubscriptionEntity.kt @@ -0,0 +1,41 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.database.entities + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription +import jakarta.persistence.* +import java.io.Serializable + +@Embeddable +class PnoFleetSegmentSubscriptionId( + val controlUnitId: Int, + @Column(name = "segment", updatable = false) + val segmentCode: String, +) : Serializable + +@Entity +@Table(name = "pno_segments_subscriptions") +data class PnoFleetSegmentSubscriptionEntity( + @EmbeddedId + val id: PnoFleetSegmentSubscriptionId, +) { + fun toPriorNotificationFleetSegmentSubscription(): PriorNotificationFleetSegmentSubscription { + return PriorNotificationFleetSegmentSubscription( + controlUnitId = id.controlUnitId, + segmentCode = id.segmentCode, + segmentName = null, + ) + } + + companion object { + fun fromPriorNotificationFleetSegmentSubscription( + priorNotificationFleetSegmentSubscription: PriorNotificationFleetSegmentSubscription, + ): PnoFleetSegmentSubscriptionEntity { + return PnoFleetSegmentSubscriptionEntity( + id = + PnoFleetSegmentSubscriptionId( + controlUnitId = priorNotificationFleetSegmentSubscription.controlUnitId, + segmentCode = priorNotificationFleetSegmentSubscription.segmentCode, + ), + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoPortSubscriptionEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoPortSubscriptionEntity.kt index 6e33716127..c26c940195 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoPortSubscriptionEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoPortSubscriptionEntity.kt @@ -1,5 +1,6 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.entities +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription import jakarta.persistence.* import java.io.Serializable @@ -13,4 +14,28 @@ data class PnoPortSubscriptionEntity( val id: PnoPortSubscriptionId, @Column(name = "receive_all_pnos", updatable = false) val receiveAllPnos: Boolean, -) +) { + fun toPriorNotificationPortSubscription(): PriorNotificationPortSubscription { + return PriorNotificationPortSubscription( + controlUnitId = id.controlUnitId, + portLocode = id.portLocode, + portName = null, + hasSubscribedToAllPriorNotifications = receiveAllPnos, + ) + } + + companion object { + fun fromPriorNotificationPortSubscription( + priorNotificationPortSubscription: PriorNotificationPortSubscription, + ): PnoPortSubscriptionEntity { + return PnoPortSubscriptionEntity( + id = + PnoPortSubscriptionId( + controlUnitId = priorNotificationPortSubscription.controlUnitId, + portLocode = priorNotificationPortSubscription.portLocode, + ), + receiveAllPnos = priorNotificationPortSubscription.hasSubscribedToAllPriorNotifications, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoSegmentSubscriptionEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoSegmentSubscriptionEntity.kt deleted file mode 100644 index 2b2ba260b1..0000000000 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoSegmentSubscriptionEntity.kt +++ /dev/null @@ -1,18 +0,0 @@ -package fr.gouv.cnsp.monitorfish.infrastructure.database.entities - -import jakarta.persistence.* -import java.io.Serializable - -@Embeddable -class PnoSegmentSubscriptionId( - val controlUnitId: Int, - @Column(name = "segment", updatable = false) - val segmentCode: String, -) : Serializable - -@Entity -@Table(name = "pno_segments_subscriptions") -data class PnoSegmentSubscriptionEntity( - @EmbeddedId - val id: PnoSegmentSubscriptionId, -) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoVesselSubscriptionEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoVesselSubscriptionEntity.kt index c6df870d19..09d64783e4 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoVesselSubscriptionEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PnoVesselSubscriptionEntity.kt @@ -1,5 +1,6 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.entities +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription import jakarta.persistence.Embeddable import jakarta.persistence.EmbeddedId import jakarta.persistence.Entity @@ -14,4 +15,30 @@ class PnoVesselSubscriptionId(val controlUnitId: Int, val vesselId: Int) : Seria data class PnoVesselSubscriptionEntity( @EmbeddedId val id: PnoVesselSubscriptionId, -) +) { + fun toPriorNotificationVesselSubscription(): PriorNotificationVesselSubscription { + return PriorNotificationVesselSubscription( + controlUnitId = id.controlUnitId, + vesselId = id.vesselId, + vesselCallSign = null, + vesselCfr = null, + vesselExternalMarking = null, + vesselMmsi = null, + vesselName = null, + ) + } + + companion object { + fun fromPriorNotificationVesselSubscription( + priorNotificationVesselSubscription: PriorNotificationVesselSubscription, + ): PnoVesselSubscriptionEntity { + return PnoVesselSubscriptionEntity( + id = + PnoVesselSubscriptionId( + controlUnitId = priorNotificationVesselSubscription.controlUnitId, + vesselId = priorNotificationVesselSubscription.vesselId, + ), + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoFleetSegmentSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoFleetSegmentSubscriptionRepository.kt new file mode 100644 index 0000000000..3fb6c31429 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoFleetSegmentSubscriptionRepository.kt @@ -0,0 +1,49 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories + +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription +import fr.gouv.cnsp.monitorfish.domain.repositories.PnoFleetSegmentSubscriptionRepository +import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoFleetSegmentSubscriptionEntity +import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces.DBPnoFleetSegmentsSubscriptionsRepository +import jakarta.transaction.Transactional +import org.springframework.stereotype.Repository + +@Repository +class JpaPnoFleetSegmentSubscriptionRepository( + private val dbPnoFleetSegmentsSubscriptionsRepository: DBPnoFleetSegmentsSubscriptionsRepository, +) : PnoFleetSegmentSubscriptionRepository { + @Transactional + override fun deleteByControlUnitId(controlUnitId: Int) { + dbPnoFleetSegmentsSubscriptionsRepository.deleteByControlUnitId(controlUnitId) + } + + override fun findAll(): List { + return dbPnoFleetSegmentsSubscriptionsRepository.findAll() + .map { it.toPriorNotificationFleetSegmentSubscription() } + } + + override fun findByControlUnitId(controlUnitId: Int): List { + return dbPnoFleetSegmentsSubscriptionsRepository.findByControlUnitId(controlUnitId) + .map { it.toPriorNotificationFleetSegmentSubscription() } + } + + override fun has( + portLocode: String, + segmentCodes: List, + ): Boolean { + return dbPnoFleetSegmentsSubscriptionsRepository.countByPortLocodeAndSegmentCodes(portLocode, segmentCodes) > 0 + } + + @Transactional + override fun saveAll( + priorNotificationFleetSegmentSubscriptions: List, + ): List { + return dbPnoFleetSegmentsSubscriptionsRepository.saveAll( + priorNotificationFleetSegmentSubscriptions.map { + PnoFleetSegmentSubscriptionEntity.fromPriorNotificationFleetSegmentSubscription( + it, + ) + }, + ) + .map { it.toPriorNotificationFleetSegmentSubscription() } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoPortSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoPortSubscriptionRepository.kt index bdf5fd6da1..c6daf99ed3 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoPortSubscriptionRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoPortSubscriptionRepository.kt @@ -1,14 +1,46 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription import fr.gouv.cnsp.monitorfish.domain.repositories.PnoPortSubscriptionRepository +import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoPortSubscriptionEntity import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces.DBPnoPortsSubscriptionsRepository +import jakarta.transaction.Transactional import org.springframework.stereotype.Repository @Repository class JpaPnoPortSubscriptionRepository( private val dbPnoPortsSubscriptionsRepository: DBPnoPortsSubscriptionsRepository, ) : PnoPortSubscriptionRepository { + @Transactional + override fun deleteByControlUnitId(controlUnitId: Int) { + dbPnoPortsSubscriptionsRepository.deleteByControlUnitId(controlUnitId) + } + + override fun findAll(): List { + return dbPnoPortsSubscriptionsRepository.findAll() + .map { it.toPriorNotificationPortSubscription() } + } + + override fun findByControlUnitId(controlUnitId: Int): List { + return dbPnoPortsSubscriptionsRepository.findByControlUnitId(controlUnitId) + .map { it.toPriorNotificationPortSubscription() } + } + override fun has(portLocode: String): Boolean { return dbPnoPortsSubscriptionsRepository.countByPortLocode(portLocode) > 0 } + + @Transactional + override fun saveAll( + priorNotificationPortSubscriptions: List, + ): List { + return dbPnoPortsSubscriptionsRepository.saveAll( + priorNotificationPortSubscriptions.map { + PnoPortSubscriptionEntity.fromPriorNotificationPortSubscription( + it, + ) + }, + ) + .map { it.toPriorNotificationPortSubscription() } + } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoSegmentSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoSegmentSubscriptionRepository.kt deleted file mode 100644 index 5e92c76e6c..0000000000 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoSegmentSubscriptionRepository.kt +++ /dev/null @@ -1,17 +0,0 @@ -package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories - -import fr.gouv.cnsp.monitorfish.domain.repositories.PnoSegmentSubscriptionRepository -import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces.DBPnoSegmentsSubscriptionsRepository -import org.springframework.stereotype.Repository - -@Repository -class JpaPnoSegmentSubscriptionRepository( - private val dbPnoSegmentsSubscriptionsRepository: DBPnoSegmentsSubscriptionsRepository, -) : PnoSegmentSubscriptionRepository { - override fun has( - portLocode: String, - segmentCodes: List, - ): Boolean { - return dbPnoSegmentsSubscriptionsRepository.countByPortLocodeAndSegmentCodes(portLocode, segmentCodes) > 0 - } -} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoVesselSubscriptionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoVesselSubscriptionRepository.kt index ac322b90dd..cff5ee3b1d 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoVesselSubscriptionRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPnoVesselSubscriptionRepository.kt @@ -1,14 +1,46 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription import fr.gouv.cnsp.monitorfish.domain.repositories.PnoVesselSubscriptionRepository +import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoVesselSubscriptionEntity import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces.DBPnoVesselsSubscriptionsRepository +import jakarta.transaction.Transactional import org.springframework.stereotype.Repository @Repository class JpaPnoVesselSubscriptionRepository( private val dbPnoVesselsSubscriptionsRepository: DBPnoVesselsSubscriptionsRepository, ) : PnoVesselSubscriptionRepository { + @Transactional + override fun deleteByControlUnitId(controlUnitId: Int) { + dbPnoVesselsSubscriptionsRepository.deleteByControlUnitId(controlUnitId) + } + + override fun findAll(): List { + return dbPnoVesselsSubscriptionsRepository.findAll() + .map { it.toPriorNotificationVesselSubscription() } + } + + override fun findByControlUnitId(controlUnitId: Int): List { + return dbPnoVesselsSubscriptionsRepository.findByControlUnitId(controlUnitId) + .map { it.toPriorNotificationVesselSubscription() } + } + override fun has(vesselId: Int): Boolean { return dbPnoVesselsSubscriptionsRepository.countByVesselId(vesselId) > 0 } + + @Transactional + override fun saveAll( + priorNotificationVesselSubscriptions: List, + ): List { + return dbPnoVesselsSubscriptionsRepository.saveAll( + priorNotificationVesselSubscriptions.map { + PnoVesselSubscriptionEntity.fromPriorNotificationVesselSubscription( + it, + ) + }, + ) + .map { it.toPriorNotificationVesselSubscription() } + } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoSegmentsSubscriptionsRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoFleetSegmentsSubscriptionsRepository.kt similarity index 53% rename from backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoSegmentsSubscriptionsRepository.kt rename to backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoFleetSegmentsSubscriptionsRepository.kt index b5b6bf369f..456199cda4 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoSegmentsSubscriptionsRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoFleetSegmentsSubscriptionsRepository.kt @@ -1,11 +1,13 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces -import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoSegmentSubscriptionEntity -import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoSegmentSubscriptionId +import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoFleetSegmentSubscriptionEntity +import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoFleetSegmentSubscriptionId import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query -interface DBPnoSegmentsSubscriptionsRepository : JpaRepository { +interface DBPnoFleetSegmentsSubscriptionsRepository : + JpaRepository { @Query( """ SELECT COUNT(*) @@ -19,4 +21,11 @@ interface DBPnoSegmentsSubscriptionsRepository : JpaRepository, ): Long + + @Modifying + @Query("DELETE FROM pno_segments_subscriptions WHERE control_unit_id = :controlUnitId", nativeQuery = true) + fun deleteByControlUnitId(controlUnitId: Int) + + @Query("SELECT * FROM pno_segments_subscriptions WHERE control_unit_id = :controlUnitId", nativeQuery = true) + fun findByControlUnitId(controlUnitId: Int): List } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoPortsSubscriptionsRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoPortsSubscriptionsRepository.kt index f0a0b006f4..91af5992af 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoPortsSubscriptionsRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoPortsSubscriptionsRepository.kt @@ -3,6 +3,7 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoPortSubscriptionEntity import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoPortSubscriptionId import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query interface DBPnoPortsSubscriptionsRepository : JpaRepository { @@ -11,4 +12,11 @@ interface DBPnoPortsSubscriptionsRepository : JpaRepository } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoVesselsSubscriptionsRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoVesselsSubscriptionsRepository.kt index 10550d4af9..3a32130249 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoVesselsSubscriptionsRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBPnoVesselsSubscriptionsRepository.kt @@ -3,9 +3,17 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoVesselSubscriptionEntity import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.PnoVesselSubscriptionId import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query interface DBPnoVesselsSubscriptionsRepository : JpaRepository { @Query("SELECT COUNT(*) FROM pno_vessels_subscriptions WHERE vessel_id = :vesselId", nativeQuery = true) fun countByVesselId(vesselId: Int): Long + + @Modifying + @Query("DELETE FROM pno_vessels_subscriptions WHERE control_unit_id = :controlUnitId", nativeQuery = true) + fun deleteByControlUnitId(controlUnitId: Int) + + @Query("SELECT * FROM pno_vessels_subscriptions WHERE control_unit_id = :controlUnitId", nativeQuery = true) + fun findByControlUnitId(controlUnitId: Int): List } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt index 764de431ce..4a21fdc37e 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepository.kt @@ -2,8 +2,8 @@ package fr.gouv.cnsp.monitorfish.infrastructure.monitorenv import fr.gouv.cnsp.monitorfish.config.ApiClient import fr.gouv.cnsp.monitorfish.config.MonitorenvProperties -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit import fr.gouv.cnsp.monitorfish.domain.repositories.ControlUnitRepository +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnit import io.ktor.client.call.* import io.ktor.client.request.* import kotlinx.coroutines.runBlocking @@ -20,9 +20,9 @@ class APIControlUnitRepository( private val logger: Logger = LoggerFactory.getLogger(APIControlUnitRepository::class.java) @Cacheable(value = ["control_units"]) - override fun findAll(): List = + override fun findAll(): List = runBlocking { - val missionsUrl = "${monitorenvProperties.url}/api/v1/control_units" + val missionsUrl = "${monitorenvProperties.url}/api/v2/control_units" try { apiClient.httpClient.get(missionsUrl).body() diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APILegacyControlUnitRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APILegacyControlUnitRepository.kt new file mode 100644 index 0000000000..30f20186e1 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APILegacyControlUnitRepository.kt @@ -0,0 +1,35 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.monitorenv + +import fr.gouv.cnsp.monitorfish.config.ApiClient +import fr.gouv.cnsp.monitorfish.config.MonitorenvProperties +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit +import fr.gouv.cnsp.monitorfish.domain.repositories.LegacyControlUnitRepository +import io.ktor.client.call.* +import io.ktor.client.request.* +import kotlinx.coroutines.runBlocking +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Repository + +@Repository +class APILegacyControlUnitRepository( + val monitorenvProperties: MonitorenvProperties, + val apiClient: ApiClient, +) : LegacyControlUnitRepository { + private val logger: Logger = LoggerFactory.getLogger(APILegacyControlUnitRepository::class.java) + + @Cacheable(value = ["legacy_control_units"]) + override fun findAll(): List = + runBlocking { + val missionsUrl = "${monitorenvProperties.url}/api/v1/control_units" + + try { + apiClient.httpClient.get(missionsUrl).body() + } catch (e: Exception) { + logger.error("Could not fetch control units at $missionsUrl", e) + + listOf() + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt index 76c23b6e90..c269bf54fc 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIMissionRepository.kt @@ -3,7 +3,7 @@ package fr.gouv.cnsp.monitorfish.infrastructure.monitorenv import com.github.benmanes.caffeine.cache.Caffeine import fr.gouv.cnsp.monitorfish.config.ApiClient import fr.gouv.cnsp.monitorfish.config.MonitorenvProperties -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.Mission import fr.gouv.cnsp.monitorfish.domain.exceptions.CouldNotFindException import fr.gouv.cnsp.monitorfish.domain.repositories.MissionRepository @@ -33,12 +33,12 @@ class APIMissionRepository( Caffeine.newBuilder() .maximumSize(500) .expireAfterWrite(1, TimeUnit.DAYS) - .build>() + .build>() override fun findControlUnitsOfMission( scope: CoroutineScope, missionId: Int, - ): Deferred> { + ): Deferred> { val cacheKey = "control_units_$missionId" val cachedControlUnits = cache.getIfPresent(cacheKey) @@ -74,26 +74,32 @@ class APIMissionRepository( // For these parameters, if the list is null or empty, we don't send the param to the server to avoid filtering results val missionTypesParameter = if (!missionTypes.isNullOrEmpty()) { - "missionTypes=${missionTypes.joinToString( - ",", - )}&" + "missionTypes=${ + missionTypes.joinToString( + ",", + ) + }&" } else { "" } val missionStatusesParameter = if (!missionStatuses.isNullOrEmpty()) { - "missionStatus=${missionStatuses.joinToString( - ",", - )}&" + "missionStatus=${ + missionStatuses.joinToString( + ",", + ) + }&" } else { "" } val seaFrontsParameter = if (!seaFronts.isNullOrEmpty()) "seaFronts=${seaFronts.joinToString(",")}&" else "" val missionSourcesParameter = if (!missionSources.isNullOrEmpty()) { - "missionSource=${missionSources.joinToString( - ",", - )}&" + "missionSource=${ + missionSources.joinToString( + ",", + ) + }&" } else { "" } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/input/MissionDataResponse.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/input/MissionDataResponse.kt index 5a2baca1a7..e77459df9a 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/input/MissionDataResponse.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/input/MissionDataResponse.kt @@ -1,17 +1,18 @@ package fr.gouv.cnsp.monitorfish.infrastructure.monitorenv.input +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.* import kotlinx.serialization.Serializable import java.time.ZonedDateTime /** - @see monitorenv/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/missions/MissionEntity.kt - for the full entity structure +@see monitorenv/backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/missions/MissionEntity.kt +for the full entity structure */ @Serializable data class MissionDataResponse( val id: Int, - val controlUnits: List = listOf(), + val controlUnits: List = listOf(), val missionTypes: List, val openBy: String? = null, val completedBy: String? = null, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/utils/StringUtils.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/utils/StringUtils.kt new file mode 100644 index 0000000000..345bf9f8e0 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/utils/StringUtils.kt @@ -0,0 +1,11 @@ +package fr.gouv.cnsp.monitorfish.utils + +import java.text.Normalizer + +object StringUtils { + fun removeAccents(input: String): String { + val normalizedInput = Normalizer.normalize(input, Normalizer.Form.NFD) + + return normalizedInput.replace("\\p{InCombiningDiacriticalMarks}+".toRegex(), "") + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/AddReportingUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/AddReportingUTests.kt index 14767267d9..3a8e9f5994 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/AddReportingUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/AddReportingUTests.kt @@ -6,7 +6,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.alerts.type.ThreeMilesTrawlingAl import fr.gouv.cnsp.monitorfish.domain.entities.reporting.* import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import fr.gouv.cnsp.monitorfish.domain.use_cases.reporting.AddReporting import fr.gouv.cnsp.monitorfish.domain.use_cases.reporting.GetInfractionSuspicionWithDMLAndSeaFront import org.assertj.core.api.Assertions.assertThat @@ -29,7 +29,7 @@ class AddReportingUTests { private lateinit var getInfractionSuspicionWithDMLAndSeaFront: GetInfractionSuspicionWithDMLAndSeaFront @Mock - private lateinit var getAllControlUnits: GetAllControlUnits + private lateinit var getAllLegacyControlUnits: GetAllLegacyControlUnits @Test fun `execute Should throw an exception When the reporting is an alert`() { @@ -54,7 +54,11 @@ class AddReportingUTests { // When val throwable = catchThrowable { - AddReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + AddReporting( + reportingRepository, + getInfractionSuspicionWithDMLAndSeaFront, + getAllLegacyControlUnits, + ).execute( reportingToAdd, ) } @@ -95,7 +99,11 @@ class AddReportingUTests { // When val throwable = catchThrowable { - AddReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + AddReporting( + reportingRepository, + getInfractionSuspicionWithDMLAndSeaFront, + getAllLegacyControlUnits, + ).execute( reportingToAdd, ) } @@ -149,7 +157,7 @@ class AddReportingUTests { given(reportingRepository.save(any())).willReturn(reportingToAdd) // When - AddReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + AddReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllLegacyControlUnits).execute( reportingToAdd, ) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetAllCurrentReportingsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetAllCurrentReportingsUTests.kt index 1e94d43200..3c1ce94a73 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetAllCurrentReportingsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetAllCurrentReportingsUTests.kt @@ -11,7 +11,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import fr.gouv.cnsp.monitorfish.domain.use_cases.reporting.GetAllCurrentReportings import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.catchThrowable @@ -30,7 +30,7 @@ class GetAllCurrentReportingsUTests { private lateinit var vesselRepository: VesselRepository @MockBean - private lateinit var getAllControlUnits: GetAllControlUnits + private lateinit var getAllLegacyControlUnits: GetAllLegacyControlUnits @Test fun `execute Should get all reportings with the underCharter field`() { @@ -68,7 +68,7 @@ class GetAllCurrentReportingsUTests { GetAllCurrentReportings( reportingRepository, vesselRepository, - getAllControlUnits, + getAllLegacyControlUnits, ).execute() // Then @@ -105,7 +105,7 @@ class GetAllCurrentReportingsUTests { // When val throwable = catchThrowable { - GetAllCurrentReportings(reportingRepository, vesselRepository, getAllControlUnits).execute() + GetAllCurrentReportings(reportingRepository, vesselRepository, getAllLegacyControlUnits).execute() } // Then diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/UpdateReportingUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/UpdateReportingUTests.kt index b40d46220b..660f732408 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/UpdateReportingUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/UpdateReportingUTests.kt @@ -6,7 +6,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.alerts.type.ThreeMilesTrawlingAl import fr.gouv.cnsp.monitorfish.domain.entities.reporting.* import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import fr.gouv.cnsp.monitorfish.domain.use_cases.reporting.GetInfractionSuspicionWithDMLAndSeaFront import fr.gouv.cnsp.monitorfish.domain.use_cases.reporting.UpdateReporting import fr.gouv.cnsp.monitorfish.domain.use_cases.reporting.UpdatedInfractionSuspicionOrObservation @@ -29,7 +29,7 @@ class UpdateReportingUTests { private lateinit var getInfractionSuspicionWithDMLAndSeaFront: GetInfractionSuspicionWithDMLAndSeaFront @MockBean - private lateinit var getAllControlUnits: GetAllControlUnits + private lateinit var getAllLegacyControlUnits: GetAllLegacyControlUnits @Test fun `execute Should throw an exception When the reporting is an alert`() { @@ -55,7 +55,7 @@ class UpdateReportingUTests { // When val throwable = catchThrowable { - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits) + UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllLegacyControlUnits) .execute( 1, UpdatedInfractionSuspicionOrObservation( @@ -101,7 +101,7 @@ class UpdateReportingUTests { // When val throwable = catchThrowable { - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits) + UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllLegacyControlUnits) .execute( 1, UpdatedInfractionSuspicionOrObservation( @@ -161,7 +161,7 @@ class UpdateReportingUTests { // When val throwable = catchThrowable { - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits) + UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllLegacyControlUnits) .execute( 1, UpdatedInfractionSuspicionOrObservation( @@ -218,7 +218,7 @@ class UpdateReportingUTests { UpdateReporting( reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, - getAllControlUnits, + getAllLegacyControlUnits, ).execute( 1, UpdatedInfractionSuspicionOrObservation( @@ -265,7 +265,11 @@ class UpdateReportingUTests { given(reportingRepository.update(any(), isA())).willReturn(reporting) // When - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + UpdateReporting( + reportingRepository, + getInfractionSuspicionWithDMLAndSeaFront, + getAllLegacyControlUnits, + ).execute( 1, UpdatedInfractionSuspicionOrObservation( reportingActor = ReportingActor.UNIT, @@ -315,7 +319,11 @@ class UpdateReportingUTests { given(reportingRepository.update(any(), isA())).willReturn(reporting) // When - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + UpdateReporting( + reportingRepository, + getInfractionSuspicionWithDMLAndSeaFront, + getAllLegacyControlUnits, + ).execute( 1, UpdatedInfractionSuspicionOrObservation( reportingActor = ReportingActor.UNIT, @@ -378,7 +386,11 @@ class UpdateReportingUTests { ) // When - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + UpdateReporting( + reportingRepository, + getInfractionSuspicionWithDMLAndSeaFront, + getAllLegacyControlUnits, + ).execute( 1, UpdatedInfractionSuspicionOrObservation( reportingActor = ReportingActor.UNIT, @@ -427,7 +439,11 @@ class UpdateReportingUTests { given(reportingRepository.update(any(), isA())).willReturn(reporting) // When - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + UpdateReporting( + reportingRepository, + getInfractionSuspicionWithDMLAndSeaFront, + getAllLegacyControlUnits, + ).execute( 1, UpdatedInfractionSuspicionOrObservation( reportingActor = ReportingActor.UNIT, @@ -483,7 +499,11 @@ class UpdateReportingUTests { ) // When - UpdateReporting(reportingRepository, getInfractionSuspicionWithDMLAndSeaFront, getAllControlUnits).execute( + UpdateReporting( + reportingRepository, + getInfractionSuspicionWithDMLAndSeaFront, + getAllLegacyControlUnits, + ).execute( 1, UpdatedInfractionSuspicionOrObservation( reportingActor = ReportingActor.UNIT, diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt index d66f12c97e..0dd1ee3137 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt @@ -4,7 +4,7 @@ import com.neovisionaries.i18n.CountryCode import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.given -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.Mission import fr.gouv.cnsp.monitorfish.domain.entities.mission.MissionSource import fr.gouv.cnsp.monitorfish.domain.entities.mission.MissionType @@ -940,7 +940,7 @@ class GetActivityReportsUTests { missionTypes = listOf(MissionType.SEA), missionSource = MissionSource.MONITORFISH, isUnderJdp = true, - controlUnits = listOf(ControlUnit(123, "AECP", false, "Unit AECP", listOf())), + controlUnits = listOf(LegacyControlUnit(123, "AECP", false, "Unit AECP", listOf())), isGeometryComputedFromControls = false, startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), ), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationITests.kt index b9d7761d01..dd959fa80e 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationITests.kt @@ -38,7 +38,7 @@ class CreateOrUpdateManualPriorNotificationITests : AbstractDBTests() { private lateinit var pnoPortSubscriptionRepository: PnoPortSubscriptionRepository @Autowired - private lateinit var pnoSegmentSubscriptionRepository: PnoSegmentSubscriptionRepository + private lateinit var pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository @Autowired private lateinit var pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt index b656cf82ac..1606e4b034 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt @@ -27,7 +27,7 @@ class CreateOrUpdateManualPriorNotificationUTests { private lateinit var pnoPortSubscriptionRepository: PnoPortSubscriptionRepository @MockBean - private lateinit var pnoSegmentSubscriptionRepository: PnoSegmentSubscriptionRepository + private lateinit var pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository @MockBean private lateinit var pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository @@ -76,8 +76,8 @@ class CreateOrUpdateManualPriorNotificationUTests { CreateOrUpdateManualPriorNotification( gearRepository, manualPriorNotificationRepository, + pnoFleetSegmentSubscriptionRepository, pnoPortSubscriptionRepository, - pnoSegmentSubscriptionRepository, pnoVesselSubscriptionRepository, portRepository, priorNotificationPdfDocumentRepository, @@ -139,8 +139,8 @@ class CreateOrUpdateManualPriorNotificationUTests { CreateOrUpdateManualPriorNotification( gearRepository, manualPriorNotificationRepository, + pnoFleetSegmentSubscriptionRepository, pnoPortSubscriptionRepository, - pnoSegmentSubscriptionRepository, pnoVesselSubscriptionRepository, portRepository, priorNotificationPdfDocumentRepository, diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscriberUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscriberUTests.kt new file mode 100644 index 0000000000..46aa99c502 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscriberUTests.kt @@ -0,0 +1,175 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification + +import com.nhaarman.mockitokotlin2.given +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageException +import fr.gouv.cnsp.monitorfish.domain.repositories.* +import fr.gouv.cnsp.monitorfish.fakers.FleetSegmentFaker +import fr.gouv.cnsp.monitorfish.fakers.FullControlUnitFaker +import fr.gouv.cnsp.monitorfish.fakers.PortFaker +import fr.gouv.cnsp.monitorfish.fakers.VesselFaker +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.test.context.junit.jupiter.SpringExtension + +@ExtendWith(SpringExtension::class) +class GetPriorNotificationSubscriberUTests { + @MockBean + private lateinit var controlUnitRepository: ControlUnitRepository + + @MockBean + private lateinit var fleetSegmentRepository: FleetSegmentRepository + + @MockBean + private lateinit var pnoPortSubscriptionRepository: PnoPortSubscriptionRepository + + @MockBean + private lateinit var pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository + + @MockBean + private lateinit var pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository + + @MockBean + private lateinit var portRepository: PortRepository + + @MockBean + private lateinit var vesselRepository: VesselRepository + + @Test + fun `execute should return a PriorNotificationSubscriber when control unit exists`() { + // Given + val fakeControlUnitId = 10023 + val fakeControlUnit = FullControlUnitFaker.fakeFullControlUnit(id = fakeControlUnitId) + given(controlUnitRepository.findAll()).willReturn(listOf(fakeControlUnit)) + + val allFakeFleetSegments = + listOf( + FleetSegmentFaker.fakeFleetSegment(segment = "SEG001", segmentName = "Segment 1"), + FleetSegmentFaker.fakeFleetSegment(segment = "SEG002", segmentName = "Segment 2"), + ) + given(fleetSegmentRepository.findAll()).willReturn(allFakeFleetSegments) + + val allFakePorts = + listOf( + PortFaker.fakePort(locode = "FRABC", name = "Port 1"), + PortFaker.fakePort(locode = "ESXYZ", name = "Port 2"), + ) + given(portRepository.findAll()).willReturn(allFakePorts) + + val allFakeVessels = + listOf( + VesselFaker.fakeVessel( + id = 1, + vesselName = "Vessel 1", + internalReferenceNumber = "CFR001", + externalReferenceNumber = "EXT001", + ircs = "CALLSIGN01", + mmsi = "MMSI01", + ), + VesselFaker.fakeVessel( + id = 2, + vesselName = "Vessel 2", + internalReferenceNumber = "CFR002", + externalReferenceNumber = "EXT002", + ircs = "CALLSIGN02", + mmsi = "MMSI02", + ), + ) + given(vesselRepository.findAll()).willReturn(allFakeVessels) + + val fakeFleetSegmentSubscriptions = + listOf( + PriorNotificationFleetSegmentSubscription( + controlUnitId = fakeControlUnitId, + segmentCode = "SEG001", + segmentName = null, // Expecting the use case to populate this + ), + ) + given(pnoFleetSegmentSubscriptionRepository.findByControlUnitId(fakeControlUnitId)) + .willReturn(fakeFleetSegmentSubscriptions) + + val portSubscriptions = + listOf( + PriorNotificationPortSubscription( + controlUnitId = fakeControlUnitId, + portLocode = "FRABC", + portName = null, // Expecting the use case to populate this + hasSubscribedToAllPriorNotifications = false, + ), + ) + given(pnoPortSubscriptionRepository.findByControlUnitId(fakeControlUnitId)) + .willReturn(portSubscriptions) + + val vesselSubscriptions = + listOf( + PriorNotificationVesselSubscription( + controlUnitId = fakeControlUnitId, + vesselId = 1, + // Expecting the use case to populate these + vesselCallSign = null, + vesselCfr = null, + vesselExternalMarking = null, + vesselMmsi = null, + vesselName = null, + ), + ) + given(pnoVesselSubscriptionRepository.findByControlUnitId(fakeControlUnitId)) + .willReturn(vesselSubscriptions) + + // When + val result = + GetPriorNotificationSubscriber( + controlUnitRepository, + fleetSegmentRepository, + pnoPortSubscriptionRepository, + pnoFleetSegmentSubscriptionRepository, + pnoVesselSubscriptionRepository, + portRepository, + vesselRepository, + ).execute(fakeControlUnitId) + + // Then + assertThat(result.controlUnit).isEqualTo(fakeControlUnit) + assertThat(result.fleetSegmentSubscriptions).containsExactly( + fakeFleetSegmentSubscriptions[0].copy(segmentName = "Segment 1"), + ) + assertThat(result.portSubscriptions).containsExactly(portSubscriptions[0].copy(portName = "Port 1")) + assertThat(result.vesselSubscriptions).containsExactly( + vesselSubscriptions[0].copy( + vesselCallSign = "CALLSIGN01", + vesselCfr = "CFR001", + vesselExternalMarking = "EXT001", + vesselMmsi = "MMSI01", + vesselName = "Vessel 1", + ), + ) + } + + @Test + fun `execute Should throw BackendUsageException when control unit does not exist`() { + // Given + val controlUnitId = 99999 + given(controlUnitRepository.findAll()).willReturn(emptyList()) + + // When / Then + val exception = + assertThrows { + GetPriorNotificationSubscriber( + controlUnitRepository, + fleetSegmentRepository, + pnoPortSubscriptionRepository, + pnoFleetSegmentSubscriptionRepository, + pnoVesselSubscriptionRepository, + portRepository, + vesselRepository, + ).execute(controlUnitId) + } + assertThat(exception.code).isEqualTo(BackendUsageErrorCode.NOT_FOUND) + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscribersUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscribersUTests.kt new file mode 100644 index 0000000000..2a061231ed --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationSubscribersUTests.kt @@ -0,0 +1,349 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification + +import com.nhaarman.mockitokotlin2.given +import fr.gouv.cnsp.monitorfish.domain.entities.administration.Administration +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationFleetSegmentSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationPortSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationVesselSubscription +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.filters.PriorNotificationSubscribersFilter +import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.sorters.PriorNotificationSubscribersSortColumn +import fr.gouv.cnsp.monitorfish.domain.repositories.* +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnit +import fr.gouv.cnsp.monitorfish.fakers.* +import junit.framework.TestCase.assertEquals +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.junit.jupiter.MockitoExtension +import org.springframework.data.domain.Sort + +@ExtendWith(MockitoExtension::class) +class GetPriorNotificationSubscribersUTests { + @Mock + private lateinit var controlUnitRepository: ControlUnitRepository + + @Mock + private lateinit var fleetSegmentRepository: FleetSegmentRepository + + @Mock + private lateinit var pnoPortSubscriptionRepository: PnoPortSubscriptionRepository + + @Mock + private lateinit var pnoFleetSegmentSubscriptionRepository: PnoFleetSegmentSubscriptionRepository + + @Mock + private lateinit var pnoVesselSubscriptionRepository: PnoVesselSubscriptionRepository + + @Mock + private lateinit var portRepository: PortRepository + + @Mock + private lateinit var vesselRepository: VesselRepository + + @Test + fun `execute Should return all prior notification subscribers when no filter is applied`() { + // Given + val fakeFullControlUnit1 = FullControlUnitFaker.fakeFullControlUnit(id = 1, name = "Control Unit 1") + val fakeFullControlUnit2 = FullControlUnitFaker.fakeFullControlUnit(id = 2, name = "Control Unit 2") + given(controlUnitRepository.findAll()).willReturn(listOf(fakeFullControlUnit1, fakeFullControlUnit2)) + + val fakeFleetSegment1 = FleetSegmentFaker.fakeFleetSegment(segment = "SEG001", segmentName = "Segment 1") + val fakeFleetSegment2 = FleetSegmentFaker.fakeFleetSegment(segment = "SEG002", segmentName = "Segment 2") + given(fleetSegmentRepository.findAll()).willReturn(listOf(fakeFleetSegment1, fakeFleetSegment2)) + + val fakePort1 = PortFaker.fakePort(locode = "FRABC", name = "Port ABC") + val fakePort2 = PortFaker.fakePort(locode = "ESXYZ", name = "Port XYZ") + given(portRepository.findAll()).willReturn(listOf(fakePort1, fakePort2)) + + val fakeVessel1 = + VesselFaker.fakeVessel( + id = 1, + internalReferenceNumber = "CFR001", + ircs = "CALLSIGN01", + externalReferenceNumber = "EXT001", + mmsi = "MMSI01", + vesselName = "Vessel 1", + ) + val fakeVessel2 = + VesselFaker.fakeVessel( + id = 2, + internalReferenceNumber = "CFR002", + ircs = "CALLSIGN02", + externalReferenceNumber = "EXT002", + mmsi = "MMSI02", + vesselName = "Vessel 2", + ) + given(vesselRepository.findAll()).willReturn(listOf(fakeVessel1, fakeVessel2)) + + val fakeFleetSegmentSubscriptions = + listOf( + PriorNotificationFleetSegmentSubscription( + controlUnitId = 1, + segmentCode = "SEG001", + segmentName = null, + ), + PriorNotificationFleetSegmentSubscription( + controlUnitId = 2, + segmentCode = "SEG002", + segmentName = null, + ), + ) + + given(pnoFleetSegmentSubscriptionRepository.findAll()).willReturn(fakeFleetSegmentSubscriptions) + + val fakePortSubscriptions = + listOf( + PriorNotificationPortSubscription( + controlUnitId = 1, + portLocode = "FRABC", + portName = null, + hasSubscribedToAllPriorNotifications = false, + ), + PriorNotificationPortSubscription( + controlUnitId = 2, + portLocode = "ESXYZ", + portName = null, + hasSubscribedToAllPriorNotifications = false, + ), + ) + + given(pnoPortSubscriptionRepository.findAll()).willReturn(fakePortSubscriptions) + + val fakeVesselSubscriptions = + listOf( + PriorNotificationVesselSubscription( + controlUnitId = 1, + vesselId = 1, + vesselCallSign = null, + vesselCfr = null, + vesselExternalMarking = null, + vesselMmsi = null, + vesselName = null, + ), + PriorNotificationVesselSubscription( + controlUnitId = 2, + vesselId = 2, + vesselCallSign = null, + vesselCfr = null, + vesselExternalMarking = null, + vesselMmsi = null, + vesselName = null, + ), + ) + + given(pnoVesselSubscriptionRepository.findAll()).willReturn(fakeVesselSubscriptions) + + // When + val useCase = + GetPriorNotificationSubscribers( + controlUnitRepository, + fleetSegmentRepository, + pnoPortSubscriptionRepository, + pnoFleetSegmentSubscriptionRepository, + pnoVesselSubscriptionRepository, + portRepository, + vesselRepository, + ) + + val result = + useCase.execute( + filter = PriorNotificationSubscribersFilter(), + sortColumn = PriorNotificationSubscribersSortColumn.CONTROL_UNIT_NAME, + sortDirection = Sort.Direction.ASC, + ) + + // Then + assertThat(result).hasSize(2) + + assertThat(result[0].controlUnit).isEqualTo(fakeFullControlUnit1) + assertThat(result[0].fleetSegmentSubscriptions).isEqualTo( + listOf(fakeFleetSegmentSubscriptions[0].copy(segmentName = "Segment 1")), + ) + assertThat(result[0].portSubscriptions).isEqualTo( + listOf(fakePortSubscriptions[0].copy(portName = "Port ABC")), + ) + assertThat(result[0].vesselSubscriptions).isEqualTo( + listOf( + fakeVesselSubscriptions[0].copy( + vesselCallSign = "CALLSIGN01", + vesselCfr = "CFR001", + vesselExternalMarking = "EXT001", + vesselMmsi = "MMSI01", + vesselName = "Vessel 1", + ), + ), + ) + + assertThat(result[1].controlUnit).isEqualTo(fakeFullControlUnit2) + assertThat(result[1].fleetSegmentSubscriptions).isEqualTo( + listOf(fakeFleetSegmentSubscriptions[1].copy(segmentName = "Segment 2")), + ) + assertThat(result[1].portSubscriptions).isEqualTo( + listOf(fakePortSubscriptions[1].copy(portName = "Port XYZ")), + ) + assertThat(result[1].vesselSubscriptions).isEqualTo( + listOf( + fakeVesselSubscriptions[1].copy( + vesselCallSign = "CALLSIGN02", + vesselCfr = "CFR002", + vesselExternalMarking = "EXT002", + vesselMmsi = "MMSI02", + vesselName = "Vessel 2", + ), + ), + ) + } + + @Test + fun `execute Should filter prior notification subscribers as expected`() { + // Given + val fakeAdministration1 = Administration(id = 1, name = "Administration A", isArchived = false) + val fakeAdministration2 = Administration(id = 2, name = "Administration B", isArchived = false) + val fakeFullControlUnit1 = + FullControlUnitFaker.fakeFullControlUnit( + id = 100, + name = "Unite1 Sans Accent", + administration = fakeAdministration1, + ) + val fakeFullControlUnit2 = + FullControlUnitFaker.fakeFullControlUnit( + id = 101, + name = "Unité2 Avec Accent", + administration = fakeAdministration2, + ) + val fakeFullControlUnit3 = + FullControlUnitFaker.fakeFullControlUnit( + id = 102, + name = "La Dernière Unité3", + administration = fakeAdministration1, + ) + + val fakePort1 = PortFaker.fakePort(locode = "ESXYZ", name = "Le Pôrt1 Très Äccentué") + val fakePort2 = PortFaker.fakePort(locode = "FRABC", name = "Le Port2 Sans Accent") + + val fakeVessel1 = VesselFaker.fakeVessel(id = 1000, vesselName = "Navire Un") + val fakeVessel2 = VesselFaker.fakeVessel(id = 1001, vesselName = "Navire Deux") + + val fakePortSubscription1 = + PriorNotificationPortSubscription( + controlUnitId = 100, + portLocode = "ESXYZ", + portName = null, + hasSubscribedToAllPriorNotifications = false, + ) + val fakePortSubscription2 = + PriorNotificationPortSubscription( + controlUnitId = 101, + portLocode = "FRABC", + portName = null, + hasSubscribedToAllPriorNotifications = false, + ) + val fakePortSubscription3 = + PriorNotificationPortSubscription( + controlUnitId = 102, + portLocode = "ESXYZ", + portName = null, + hasSubscribedToAllPriorNotifications = false, + ) + + // Mock repository methods + `when`(controlUnitRepository.findAll()).thenReturn( + listOf( + fakeFullControlUnit1, + fakeFullControlUnit2, + fakeFullControlUnit3, + ), + ) + `when`(fleetSegmentRepository.findAll()).thenReturn(emptyList()) + `when`(pnoPortSubscriptionRepository.findAll()).thenReturn( + listOf( + fakePortSubscription1, + fakePortSubscription2, + fakePortSubscription3, + ), + ) + `when`(pnoFleetSegmentSubscriptionRepository.findAll()).thenReturn(emptyList()) + `when`(pnoVesselSubscriptionRepository.findAll()).thenReturn(emptyList()) + `when`(portRepository.findAll()).thenReturn(listOf(fakePort1, fakePort2)) + `when`(vesselRepository.findAll()).thenReturn(listOf(fakeVessel1, fakeVessel2)) + + var testCases: List, String>> = emptyList() + + // Case 1: Search query matches control unit name + testCases = testCases + + Triple( + PriorNotificationSubscribersFilter(searchQuery = "unitÉ1"), + listOf(fakeFullControlUnit1), + "Search query matches control unit name", + ) + + // Case 2: Search query matches administration name + testCases = testCases + + Triple( + PriorNotificationSubscribersFilter(searchQuery = "adminÎstratÏon b"), + listOf(fakeFullControlUnit2), + "Search query matches administration name", + ) + + // Case 3: Search query matches port name in port subscriptions + testCases = testCases + + Triple( + PriorNotificationSubscribersFilter(searchQuery = "tres accentue"), + listOf(fakeFullControlUnit1, fakeFullControlUnit3), + "Search query matches port name in port subscriptions", + ) + + // Case 4: Search query matches none + testCases = testCases + + Triple( + PriorNotificationSubscribersFilter(searchQuery = "inexistant"), + emptyList(), + "Search query matches none", + ) + + // Case 5: Combination of filters - administrationId and searchQuery + testCases = testCases + + Triple( + PriorNotificationSubscribersFilter(administrationId = 1, searchQuery = "derniere"), + listOf(fakeFullControlUnit3), + "Combination of administrationId and searchQuery filters", + ) + + // Case 6: Filter by portLocode + testCases = testCases + + Triple( + PriorNotificationSubscribersFilter(portLocode = "FRABC"), + listOf(fakeFullControlUnit2), + "Filter by portLocode", + ) + + val useCase = + GetPriorNotificationSubscribers( + controlUnitRepository, + fleetSegmentRepository, + pnoPortSubscriptionRepository, + pnoFleetSegmentSubscriptionRepository, + pnoVesselSubscriptionRepository, + portRepository, + vesselRepository, + ) + + for ((filter, expectedControlUnits, description) in testCases) { + // When + val result = + useCase.execute( + filter = filter, + sortColumn = PriorNotificationSubscribersSortColumn.CONTROL_UNIT_NAME, + sortDirection = Sort.Direction.ASC, + ) + + // Then + val expectedControlUnitIds = expectedControlUnits.map { it.id }.sorted() + val resultControlUnitIds = result.map { it.controlUnit.id }.sorted() + + assertEquals("Failed test case: $description", expectedControlUnitIds, resultControlUnitIds) + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportingsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportingsUTests.kt index 4397403bb6..4b63f4f8ca 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportingsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/reporting/GetVesselReportingsUTests.kt @@ -12,7 +12,7 @@ import fr.gouv.cnsp.monitorfish.domain.repositories.InfractionRepository import fr.gouv.cnsp.monitorfish.domain.repositories.ReportingRepository import fr.gouv.cnsp.monitorfish.domain.use_cases.TestUtils import fr.gouv.cnsp.monitorfish.domain.use_cases.TestUtils.createCurrentReporting -import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllControlUnits +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.GetAllLegacyControlUnits import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -30,7 +30,7 @@ class GetVesselReportingsUTests { private lateinit var infractionRepository: InfractionRepository @MockBean - private lateinit var getAllControlUnits: GetAllControlUnits + private lateinit var getAllLegacyControlUnits: GetAllLegacyControlUnits @Test fun `execute Should return a map of years for archived`() { @@ -45,7 +45,7 @@ class GetVesselReportingsUTests { GetVesselReportings( reportingRepository, infractionRepository, - getAllControlUnits, + getAllLegacyControlUnits, ).execute( null, "FR224226850", @@ -80,7 +80,7 @@ class GetVesselReportingsUTests { GetVesselReportings( reportingRepository, infractionRepository, - getAllControlUnits, + getAllLegacyControlUnits, ).execute( null, "FR224226850", @@ -125,7 +125,7 @@ class GetVesselReportingsUTests { GetVesselReportings( reportingRepository, infractionRepository, - getAllControlUnits, + getAllLegacyControlUnits, ).execute( 123456, "FR224226850", @@ -188,7 +188,7 @@ class GetVesselReportingsUTests { GetVesselReportings( reportingRepository, infractionRepository, - getAllControlUnits, + getAllLegacyControlUnits, ).execute( null, "FR224226850", @@ -271,7 +271,7 @@ class GetVesselReportingsUTests { GetVesselReportings( reportingRepository, infractionRepository, - getAllControlUnits, + getAllLegacyControlUnits, ).execute( null, "FR55667788", @@ -396,7 +396,7 @@ class GetVesselReportingsUTests { GetVesselReportings( reportingRepository, infractionRepository, - getAllControlUnits, + getAllLegacyControlUnits, ).execute( null, "FR55667788", diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/FleetSegmentFaker.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/FleetSegmentFaker.kt new file mode 100644 index 0000000000..3af892973b --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/FleetSegmentFaker.kt @@ -0,0 +1,31 @@ +package fr.gouv.cnsp.monitorfish.fakers + +import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment + +class FleetSegmentFaker { + companion object { + fun fakeFleetSegment( + segment: String = "SEG001", + segmentName: String = "Fake Segment Name", + dirm: List = listOf(), + gears: List = listOf("GEAR1", "GEAR2"), + faoAreas: List = listOf("FAO_AREA1", "FAO_AREA2"), + targetSpecies: List = listOf("SPECIES1", "SPECIES2"), + bycatchSpecies: List = listOf(), + impactRiskFactor: Double = 1.0, + year: Int = 2023, + ): FleetSegment { + return FleetSegment( + segment = segment, + segmentName = segmentName, + dirm = dirm, + gears = gears, + faoAreas = faoAreas, + targetSpecies = targetSpecies, + bycatchSpecies = bycatchSpecies, + impactRiskFactor = impactRiskFactor, + year = year, + ) + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/FullControlUnitFaker.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/FullControlUnitFaker.kt new file mode 100644 index 0000000000..336add85b1 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/FullControlUnitFaker.kt @@ -0,0 +1,131 @@ +package fr.gouv.cnsp.monitorfish.fakers + +import fr.gouv.cnsp.monitorfish.domain.entities.administration.Administration +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.* +import fr.gouv.cnsp.monitorfish.domain.entities.station.Station +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnit +import fr.gouv.cnsp.monitorfish.domain.use_cases.control_units.dtos.FullControlUnitResource + +class FullControlUnitFaker { + companion object { + fun fakeFullControlUnit( + id: Int = 1, + areaNote: String? = "Default Area Note", + administration: Administration = fakeAdministration(), + administrationId: Int = administration.id, + controlUnitContactIds: List = listOf(1), + controlUnitContacts: List = listOf(fakeControlUnitContact()), + controlUnitResourceIds: List = listOf(1), + controlUnitResources: List = listOf(fakeFullControlUnitResource()), + departmentArea: ControlUnitDepartmentArea? = fakeControlUnitDepartmentArea(), + departmentAreaInseeCode: String? = departmentArea?.inseeCode, + isArchived: Boolean = false, + name: String = "Fake Control Unit Name", + termsNote: String? = "Default Terms Note", + ): FullControlUnit { + return FullControlUnit( + id = id, + areaNote = areaNote, + administration = administration, + administrationId = administrationId, + controlUnitContactIds = controlUnitContactIds, + controlUnitContacts = controlUnitContacts, + controlUnitResourceIds = controlUnitResourceIds, + controlUnitResources = controlUnitResources, + departmentArea = departmentArea, + departmentAreaInseeCode = departmentAreaInseeCode, + isArchived = isArchived, + name = name, + termsNote = termsNote, + ) + } + + private fun fakeAdministration( + id: Int = 1, + isArchived: Boolean = false, + name: String = "Fake Administration Name", + ): Administration { + return Administration( + id = id, + isArchived = isArchived, + name = name, + ) + } + + private fun fakeControlUnitContact( + id: Int = 1, + controlUnitId: Int = 1, + email: String? = "contact@example.com", + isEmailSubscriptionContact: Boolean = true, + isSmsSubscriptionContact: Boolean = false, + name: String = "Fake Contact Name", + phone: String? = "+1234567890", + ): ControlUnitContact { + return ControlUnitContact( + id = id, + controlUnitId = controlUnitId, + email = email, + isEmailSubscriptionContact = isEmailSubscriptionContact, + isSmsSubscriptionContact = isSmsSubscriptionContact, + name = name, + phone = phone, + ) + } + + private fun fakeFullControlUnitResource( + id: Int = 1, + controlUnit: ControlUnit = fakeControlUnit(), + controlUnitId: Int = controlUnit.id, + isArchived: Boolean = false, + name: String = "Fake Resource Name", + note: String? = "Default Resource Note", + photo: ByteArray? = null, + station: Station = StationFaker.fakeStation(), + stationId: Int = station.id, + type: ControlUnitResourceType = ControlUnitResourceType.FRIGATE, + ): FullControlUnitResource { + return FullControlUnitResource( + id = id, + controlUnit = controlUnit, + controlUnitId = controlUnitId, + isArchived = isArchived, + name = name, + note = note, + photo = photo, + station = station, + stationId = stationId, + type = type, + ) + } + + private fun fakeControlUnit( + id: Int = 1, + areaNote: String? = "Default Area Note", + administrationId: Int = 1, + departmentAreaInseeCode: String? = "00000", + isArchived: Boolean = false, + name: String = "Fake Control Unit Name", + termsNote: String? = "Default Terms Note", + ): ControlUnit { + return ControlUnit( + id = id, + areaNote = areaNote, + administrationId = administrationId, + departmentAreaInseeCode = departmentAreaInseeCode, + isArchived = isArchived, + name = name, + termsNote = termsNote, + ) + } + + private fun fakeControlUnitDepartmentArea( + inseeCode: String = "00000", + name: String = "Fake Department Area Name", + ): ControlUnitDepartmentArea { + return ControlUnitDepartmentArea( + inseeCode = inseeCode, + name = name, + ) + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/StationFaker.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/StationFaker.kt new file mode 100644 index 0000000000..4d1d7cb21a --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/fakers/StationFaker.kt @@ -0,0 +1,21 @@ +package fr.gouv.cnsp.monitorfish.fakers + +import fr.gouv.cnsp.monitorfish.domain.entities.station.Station + +class StationFaker { + companion object { + fun fakeStation( + id: Int = 1, + latitude: Double = 48.8566, + longitude: Double = 2.3522, + name: String = "Fake Station Name", + ): Station { + return Station( + id = id, + latitude = latitude, + longitude = longitude, + name = name, + ) + } + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt index a7399e48fe..edecd1c1ca 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.neovisionaries.i18n.CountryCode import com.nhaarman.mockitokotlin2.* import fr.gouv.cnsp.monitorfish.config.SentryConfig -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.* import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.ActivityCode import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.JointDeploymentPlan @@ -374,7 +374,7 @@ class MissionActionsControllerITests { ), activityCode = ActivityCode.FIS, vesselNationalIdentifier = "AYFR000654", - controlUnits = listOf(ControlUnit(1234, "DIRM", false, "Cross Etel", listOf())), + controlUnits = listOf(LegacyControlUnit(1234, "DIRM", false, "Cross Etel", listOf())), faoArea = "27.7.c", segment = "NS01/03", vessel = diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationSubscriberControllerUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationSubscriberControllerUTests.kt new file mode 100644 index 0000000000..a1c5b76f5a --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationSubscriberControllerUTests.kt @@ -0,0 +1,155 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.bff + +import com.fasterxml.jackson.databind.ObjectMapper +import com.nhaarman.mockitokotlin2.any +import fr.gouv.cnsp.monitorfish.config.SentryConfig +import fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.* +import fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification.dtos.PriorNotificationSubscriber +import fr.gouv.cnsp.monitorfish.fakers.FullControlUnitFaker +import fr.gouv.cnsp.monitorfish.infrastructure.api.input.PriorNotificationSubscriberDataInput +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.Test +import org.mockito.BDDMockito.given +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 +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.context.annotation.Import +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* + +@Import(SentryConfig::class) +@AutoConfigureMockMvc(addFilters = false) +@WebMvcTest(value = [(PriorNotificationSubscriberController::class)]) +class PriorNotificationSubscriberControllerUTests { + @Autowired + private lateinit var api: MockMvc + + @Autowired + private lateinit var objectMapper: ObjectMapper + + @MockBean + private lateinit var getPriorNotificationSubscriber: GetPriorNotificationSubscriber + + @MockBean + private lateinit var getPriorNotificationSubscribers: GetPriorNotificationSubscribers + + @MockBean + private lateinit var updatePriorNotificationSubscriber: UpdatePriorNotificationSubscriber + + @Test + fun `getAll Should get a list of prior notification subscribers`() { + // Given + val fakeControlUnit1 = FullControlUnitFaker.fakeFullControlUnit(id = 100, name = "Control Unit 1") + val fakeControlUnit2 = FullControlUnitFaker.fakeFullControlUnit(id = 101, name = "Control Unit 2") + + val fakeSubscriber1 = + PriorNotificationSubscriber( + controlUnit = fakeControlUnit1, + fleetSegmentSubscriptions = emptyList(), + portSubscriptions = emptyList(), + vesselSubscriptions = emptyList(), + ) + val fakeSubscriber2 = + PriorNotificationSubscriber( + controlUnit = fakeControlUnit2, + fleetSegmentSubscriptions = emptyList(), + portSubscriptions = emptyList(), + vesselSubscriptions = emptyList(), + ) + + given( + getPriorNotificationSubscribers.execute( + any(), + any(), + any(), + ), + ).willReturn( + listOf(fakeSubscriber1, fakeSubscriber2), + ) + + // When + api.perform( + get("/bff/v1/prior_notification_subscribers?sortColumn=CONTROL_UNIT_NAME&sortDirection=ASC"), + ) + // Then + .andExpect(status().isOk) + .andExpect(jsonPath("$.length()", equalTo(2))) + .andExpect(jsonPath("$[0].controlUnit.id", equalTo(fakeSubscriber1.controlUnit.id))) + .andExpect(jsonPath("$[1].controlUnit.id", equalTo(fakeSubscriber2.controlUnit.id))) + } + + @Test + fun `getOne Should get a prior notification subscriber by its controlUnitId`() { + // Given + val fakeControlUnitId = 100 + val fakeControlUnit = FullControlUnitFaker.fakeFullControlUnit(id = fakeControlUnitId, name = "Control Unit 1") + + val fakeSubscriber = + PriorNotificationSubscriber( + controlUnit = fakeControlUnit, + fleetSegmentSubscriptions = emptyList(), + portSubscriptions = emptyList(), + vesselSubscriptions = emptyList(), + ) + + given( + getPriorNotificationSubscriber.execute(fakeControlUnitId), + ).willReturn(fakeSubscriber) + + // When + api.perform( + get("/bff/v1/prior_notification_subscribers/$fakeControlUnitId"), + ) + // Then + .andExpect(status().isOk) + .andExpect(jsonPath("$.controlUnit.id", equalTo(fakeControlUnitId))) + } + + @Test + fun `updateOne Should update a prior notification subscriber by its controlUnitId`() { + // Given + val fakeControlUnitId = 100 + val fakeControlUnit = FullControlUnitFaker.fakeFullControlUnit(id = fakeControlUnitId, name = "Control Unit 1") + + val fakeSubscriber = + PriorNotificationSubscriber( + controlUnit = fakeControlUnit, + fleetSegmentSubscriptions = emptyList(), + portSubscriptions = emptyList(), + vesselSubscriptions = emptyList(), + ) + + val fakeDataInput = + PriorNotificationSubscriberDataInput( + controlUnitId = fakeControlUnitId, + portLocodes = listOf("FRABC", "ESXYZ"), + portLocodesWithFullSubscription = listOf("ESXYZ"), + fleetSegmentCodes = listOf("SEG001", "SEG002"), + vesselIds = listOf(1001, 1002), + ) + + given( + updatePriorNotificationSubscriber.execute( + controlUnitId = any(), + fleetSegmentSubscriptions = any(), + portSubscriptions = any(), + vesselSubscriptions = any(), + ), + ).willReturn(fakeSubscriber) + + val requestBody = objectMapper.writeValueAsString(fakeDataInput) + + // When + api.perform( + put("/bff/v1/prior_notification_subscribers/$fakeControlUnitId") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody), + ) + // Then + .andExpect(status().isOk) + .andExpect(jsonPath("$.controlUnit.id", equalTo(fakeControlUnitId))) + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/ReportingControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/ReportingControllerITests.kt index 6a6e181b1b..26f12a75a7 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/ReportingControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/ReportingControllerITests.kt @@ -7,7 +7,7 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.given import fr.gouv.cnsp.monitorfish.config.MapperConfiguration import fr.gouv.cnsp.monitorfish.config.SentryConfig -import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit +import fr.gouv.cnsp.monitorfish.domain.entities.control_unit.LegacyControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.reporting.InfractionSuspicion import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Reporting import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingActor @@ -193,7 +193,7 @@ class ReportingControllerITests { isArchived = false, ) given(addReporting.execute(any())).willReturn( - Pair(reporting, ControlUnit(1234, "DIRM", false, "Cross Etel", listOf())), + Pair(reporting, LegacyControlUnit(1234, "DIRM", false, "Cross Etel", listOf())), ) // When diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APILegacyControlUnitRepositoryITests.kt similarity index 94% rename from backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepositoryITests.kt rename to backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APILegacyControlUnitRepositoryITests.kt index 2daeaa1e74..11a22c83e1 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APIControlUnitRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/monitorenv/APILegacyControlUnitRepositoryITests.kt @@ -8,7 +8,7 @@ import io.ktor.utils.io.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -class APIControlUnitRepositoryITests { +class APILegacyControlUnitRepositoryITests { @Test fun `findAll Should return control units`() { // Given @@ -54,7 +54,7 @@ class APIControlUnitRepositoryITests { // When val controlUnits = - APIControlUnitRepository(monitorenvProperties, apiClient) + APILegacyControlUnitRepository(monitorenvProperties, apiClient) .findAll() assertThat(controlUnits).hasSize(3) diff --git a/frontend/cypress/e2e/backoffice/control_objectives.spec.ts b/frontend/cypress/e2e/backoffice/control_objective_tables/actions.spec.ts similarity index 67% rename from frontend/cypress/e2e/backoffice/control_objectives.spec.ts rename to frontend/cypress/e2e/backoffice/control_objective_tables/actions.spec.ts index 6d363c4099..ae6394df39 100644 --- a/frontend/cypress/e2e/backoffice/control_objectives.spec.ts +++ b/frontend/cypress/e2e/backoffice/control_objective_tables/actions.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ -context('Control objectives', () => { +context('BackOffice > Control Objective Tables > Actions', () => { beforeEach(() => { const currentYear = new Date().getFullYear() cy.intercept('GET', `/bff/v1/fleet_segments/${currentYear}`).as('fleetSegments') @@ -10,39 +10,6 @@ context('Control objectives', () => { cy.wait('@controlObjectives') }) - it('Should render the objectives and navigate between years', () => { - // Then - cy.get('.rs-table-row').should('have.length', 67) - cy.get('[data-cy="control-objective-facade-title"]').should('have.length', 5) - cy.get('[data-cy="control-objective-facade-title"]').eq(0).contains('NORD ATLANTIQUE - MANCHE OUEST (NAMO)') - cy.get('[data-cy="control-objective-facade-title"]').eq(1).contains('MANCHE EST – MER DU NORD (MEMN)') - cy.get('[data-cy="control-objective-facade-title"]').eq(2).contains('SUD-ATLANTIQUE (SA)') - cy.get('[data-cy="control-objective-facade-title"]').eq(3).contains('Méditerranée (MED)') - cy.get('[data-cy="control-objective-facade-title"]').eq(4).contains('Corse (CORSE)') - - cy.get('.rs-table-cell-content').eq(9).contains('ATL01') - // We check the next line as the ATL01 segment was deleted from the segment table and has no segment name associated - cy.get('.rs-table-cell-content').eq(18).contains('Eel sea fisheries') - cy.get('.rs-table-cell-content').eq(11).children().should('have.value', '0') - cy.get('.rs-table-cell-content').eq(12).children().should('have.value', '20') - // We check the next line as the ATL01 segment was deleted from the segment table and has no impact risk factor associated - cy.get('.rs-table-cell-content').eq(21).children().contains('3.8') - cy.get('.rs-table-cell-content').eq(14).children().children().children().contains('1') - - const currentYear = new Date().getFullYear() - cy.get('*[data-cy^="control-objectives-year"]').contains(currentYear) - - cy.log('Check the FR_SCE control objectives of MEMN') - cy.get('[data-cy="row-68-targetNumberOfControlsAtPort"]').should('exist') - cy.get('[data-cy="row-68-targetNumberOfControlsAtSea"]').should('exist') - - cy.log('Navigate to previous year') - cy.get('*[data-cy^="control-objectives-year"]').click() - cy.get(`[data-key="${currentYear - 1}"] > .rs-picker-select-menu-item`).click() - cy.get('[data-cy="row-15-targetNumberOfControlsAtPort"]').should('exist') - cy.get('[data-cy="row-15-targetNumberOfControlsAtSea"]').should('exist') - }) - it('Should update the targetNumberOfControlsAtPort field on an objective', () => { // When cy.intercept('PUT', '/bff/v1/admin/control_objectives/78').as('updateObjective') @@ -162,22 +129,6 @@ context('Control objectives', () => { cy.get('.rs-table-row').should('have.length', 67) }) - it('Should permit to add a control objective year When the current year is not yet added', () => { - // Given - const currentYear = new Date().getFullYear() - const nextYear = currentYear + 1 - const now = new Date(nextYear, 3, 14).getTime() - - cy.clock(now) - cy.get('.rs-table-row').should('have.length', 67) - cy.get('*[data-cy^="control-objectives-year"]').contains(currentYear) - cy.get('*[data-cy^="control-objectives-year"]').click() - cy.get('.rs-picker-select-menu-item').should('have.length', 2) - - // Then - cy.get('*[data-cy="control-objectives-add-year"]').contains(nextYear) - }) - it('Should add the next control objective year', () => { // Given cy.get('.rs-table-row').should('have.length', 67) diff --git a/frontend/cypress/e2e/backoffice/control_objective_tables/behavior.spec.ts b/frontend/cypress/e2e/backoffice/control_objective_tables/behavior.spec.ts new file mode 100644 index 0000000000..ce32b05023 --- /dev/null +++ b/frontend/cypress/e2e/backoffice/control_objective_tables/behavior.spec.ts @@ -0,0 +1,28 @@ +/* eslint-disable no-undef */ + +context('BackOffice > Control Objective Tables > Behavior', () => { + beforeEach(() => { + const currentYear = new Date().getFullYear() + cy.intercept('GET', `/bff/v1/fleet_segments/${currentYear}`).as('fleetSegments') + cy.intercept('GET', `/bff/v1/admin/control_objectives/${currentYear}`).as('controlObjectives') + cy.visit('/backoffice/control_objectives') + cy.wait('@fleetSegments') + cy.wait('@controlObjectives') + }) + + it('Should permit to add a control objective year When the current year is not yet added', () => { + // Given + const currentYear = new Date().getFullYear() + const nextYear = currentYear + 1 + const now = new Date(nextYear, 3, 14).getTime() + + cy.clock(now) + cy.get('.rs-table-row').should('have.length', 67) + cy.get('*[data-cy^="control-objectives-year"]').contains(currentYear) + cy.get('*[data-cy^="control-objectives-year"]').click() + cy.get('.rs-picker-select-menu-item').should('have.length', 2) + + // Then + cy.get('*[data-cy="control-objectives-add-year"]').contains(nextYear) + }) +}) diff --git a/frontend/cypress/e2e/backoffice/control_objective_tables/table.spec.ts b/frontend/cypress/e2e/backoffice/control_objective_tables/table.spec.ts new file mode 100644 index 0000000000..77d69a4989 --- /dev/null +++ b/frontend/cypress/e2e/backoffice/control_objective_tables/table.spec.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-undef */ + +context('BackOffice > Control Objective Tables > Table', () => { + beforeEach(() => { + const currentYear = new Date().getFullYear() + cy.intercept('GET', `/bff/v1/fleet_segments/${currentYear}`).as('fleetSegments') + cy.intercept('GET', `/bff/v1/admin/control_objectives/${currentYear}`).as('controlObjectives') + cy.visit('/backoffice/control_objectives') + cy.wait('@fleetSegments') + cy.wait('@controlObjectives') + }) + + it('Should render the objectives and navigate between years', () => { + // Then + cy.get('.rs-table-row').should('have.length', 67) + cy.get('[data-cy="control-objective-facade-title"]').should('have.length', 5) + cy.get('[data-cy="control-objective-facade-title"]').eq(0).contains('NORD ATLANTIQUE - MANCHE OUEST (NAMO)') + cy.get('[data-cy="control-objective-facade-title"]').eq(1).contains('MANCHE EST – MER DU NORD (MEMN)') + cy.get('[data-cy="control-objective-facade-title"]').eq(2).contains('SUD-ATLANTIQUE (SA)') + cy.get('[data-cy="control-objective-facade-title"]').eq(3).contains('Méditerranée (MED)') + cy.get('[data-cy="control-objective-facade-title"]').eq(4).contains('Corse (CORSE)') + + cy.get('.rs-table-cell-content').eq(9).contains('ATL01') + // We check the next line as the ATL01 segment was deleted from the segment table and has no segment name associated + cy.get('.rs-table-cell-content').eq(18).contains('Eel sea fisheries') + cy.get('.rs-table-cell-content').eq(11).children().should('have.value', '0') + cy.get('.rs-table-cell-content').eq(12).children().should('have.value', '20') + // We check the next line as the ATL01 segment was deleted from the segment table and has no impact risk factor associated + cy.get('.rs-table-cell-content').eq(21).children().contains('3.8') + cy.get('.rs-table-cell-content').eq(14).children().children().children().contains('1') + + const currentYear = new Date().getFullYear() + cy.get('*[data-cy^="control-objectives-year"]').contains(currentYear) + + cy.log('Check the FR_SCE control objectives of MEMN') + cy.get('[data-cy="row-68-targetNumberOfControlsAtPort"]').should('exist') + cy.get('[data-cy="row-68-targetNumberOfControlsAtSea"]').should('exist') + + cy.log('Navigate to previous year') + cy.get('*[data-cy^="control-objectives-year"]').click() + cy.get(`[data-key="${currentYear - 1}"] > .rs-picker-select-menu-item`).click() + cy.get('[data-cy="row-15-targetNumberOfControlsAtPort"]').should('exist') + cy.get('[data-cy="row-15-targetNumberOfControlsAtSea"]').should('exist') + }) +}) diff --git a/frontend/cypress/e2e/backoffice/fleet_segments.spec.ts b/frontend/cypress/e2e/backoffice/fleet_segment_table.spec.ts similarity index 99% rename from frontend/cypress/e2e/backoffice/fleet_segments.spec.ts rename to frontend/cypress/e2e/backoffice/fleet_segment_table.spec.ts index 3ad29fb520..5a963be31e 100644 --- a/frontend/cypress/e2e/backoffice/fleet_segments.spec.ts +++ b/frontend/cypress/e2e/backoffice/fleet_segment_table.spec.ts @@ -6,7 +6,7 @@ import utc from 'dayjs/plugin/utc' dayjs.extend(utc) const currentYear = dayjs().utc().year() -context('Fleet segments', () => { +context('BackOffice > Fleet Segments Table', () => { beforeEach(() => { cy.intercept('GET', `/bff/v1/fleet_segments/${currentYear}`).as('fleetSegments') cy.visit('/backoffice/fleet_segments') diff --git a/frontend/cypress/e2e/backoffice/__image_snapshots__/Update Regulation The geometry Should be displayed #0.png b/frontend/cypress/e2e/backoffice/regulation_form/__image_snapshots__/BackOffice Regulation Form Edition The geometry Should be displayed #0.png similarity index 100% rename from frontend/cypress/e2e/backoffice/__image_snapshots__/Update Regulation The geometry Should be displayed #0.png rename to frontend/cypress/e2e/backoffice/regulation_form/__image_snapshots__/BackOffice Regulation Form Edition The geometry Should be displayed #0.png diff --git a/frontend/cypress/e2e/backoffice/__image_snapshots__/Update Regulation The geometry Should be displayed #1.png b/frontend/cypress/e2e/backoffice/regulation_form/__image_snapshots__/BackOffice Regulation Form Edition The geometry Should be displayed #1.png similarity index 100% rename from frontend/cypress/e2e/backoffice/__image_snapshots__/Update Regulation The geometry Should be displayed #1.png rename to frontend/cypress/e2e/backoffice/regulation_form/__image_snapshots__/BackOffice Regulation Form Edition The geometry Should be displayed #1.png diff --git a/frontend/cypress/e2e/backoffice/new_regulation.spec.ts b/frontend/cypress/e2e/backoffice/regulation_form/creation.spec.ts similarity index 97% rename from frontend/cypress/e2e/backoffice/new_regulation.spec.ts rename to frontend/cypress/e2e/backoffice/regulation_form/creation.spec.ts index bfaf2fc303..ac848cb8be 100644 --- a/frontend/cypress/e2e/backoffice/new_regulation.spec.ts +++ b/frontend/cypress/e2e/backoffice/regulation_form/creation.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ -context('New Regulation', () => { +context('BackOffice > Regulation Form > Creation', () => { beforeEach(() => { cy.visit('/backoffice/regulation/new') cy.wait(1000) @@ -48,15 +48,14 @@ context('New Regulation', () => { it('Region list should be enabled or disabled depending on the law type', () => { // Select a French law type cy.fill('Choisir un ensemble', 'Reg. MED') - cy.get('[id="Choisir une région"]').click() + cy.get('[id="Choisir une région"]').parent().click() // Region list length should be equal to 18 cy.get('[id="Choisir une région-listbox"] > div').should('have.length', 18) // Select a EU law type cy.fill('Choisir un ensemble', '2019') - cy.get('[data-key="R(UE) 2019/1241"]').eq(0).click({ force: true }) // Region select picker should be disabled - cy.get('[id="Choisir une région"]').click().should('have.attr', 'aria-disabled', 'true') + cy.get('[id="Choisir une région"]').forceClick().should('have.attr', 'aria-disabled', 'true') }) it('Select "Grand Est" and "Auvergne-Rhône-Alpes" region and remove it', () => { @@ -190,7 +189,7 @@ context('New Regulation', () => { cy.get('[data-cy="regulatory-gears-section"]').scrollIntoView().click() cy.get('[data-cy="authorized-all-towed-gears-option"] > div > label').click() cy.get('*[data-cy^="open-regulated-species"]').scrollIntoView().click({ force: true }).scrollIntoView() - cy.get('.rs-picker-toggle-placeholder').filter(':contains("catégories d\'espèces")').eq(0).click({ timeout: 10000 }) + cy.get('.rs-picker-toggle-placeholder').filter(':contains("catégories d\'espèces")').eq(0).parent().click() cy.wait(200) cy.get('.rs-search-box-input').type('Espèce{enter}', { force: true }) cy.get('.rs-picker-toggle-placeholder').filter(':contains("des espèces")').eq(0).click({ timeout: 10000 }) diff --git a/frontend/cypress/e2e/backoffice/update_regulation.spec.ts b/frontend/cypress/e2e/backoffice/regulation_form/edition.spec.ts similarity index 98% rename from frontend/cypress/e2e/backoffice/update_regulation.spec.ts rename to frontend/cypress/e2e/backoffice/regulation_form/edition.spec.ts index db064447ea..89a06b6533 100644 --- a/frontend/cypress/e2e/backoffice/update_regulation.spec.ts +++ b/frontend/cypress/e2e/backoffice/regulation_form/edition.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable no-undef */ -import { customDayjs } from '../utils/customDayjs' +import { customDayjs } from '../../utils/customDayjs' -context('Update Regulation', () => { +context('BackOffice > Regulation Form > Edition', () => { beforeEach(() => { cy.visit('/backoffice/regulation') cy.clearLocalStorage(/persist/) @@ -107,8 +107,9 @@ context('Update Regulation', () => { cy.get('.rs-picker-toggle-placeholder') .filter(':contains("catégories d\'espèces")') .eq(0) + .parent() .scrollIntoView() - .click({ timeout: 10000 }) + .click() cy.get('.rs-search-box-input').type('Espèce{enter}') cy.get('[data-cy="authorized-species-selector"]').filter(':contains("des espèces")').click({ timeout: 10000 }) cy.wait(200) @@ -119,8 +120,9 @@ context('Update Regulation', () => { cy.get('.rs-picker-toggle-placeholder') .filter(':contains("catégories d\'espèces")') .eq(1) + .parent() .scrollIntoView() - .click({ timeout: 10000 }) + .click() cy.get('.rs-search-box-input').should('have.length', 1) cy.get('.rs-search-box-input').type('Bival{enter}') cy.get('[data-cy="unauthorized-species-selector"]').filter(':contains("des espèces")').click({ timeout: 10000 }) diff --git a/frontend/cypress/e2e/backoffice/regulation_table/behavior.spec.ts b/frontend/cypress/e2e/backoffice/regulation_table/behavior.spec.ts new file mode 100644 index 0000000000..31420240df --- /dev/null +++ b/frontend/cypress/e2e/backoffice/regulation_table/behavior.spec.ts @@ -0,0 +1,37 @@ +/* eslint-disable no-undef */ + +context('BackOffice > Regulation Table > Behavior', () => { + beforeEach(() => { + cy.visit('/backoffice/regulation') + cy.wait(200) + }) + + it('on mouse over layer name edit button appears', () => { + // Given + // listen Post request to /geoserver/wfs + cy.intercept('POST', '/geoserver/wfs', { hostname: 'localhost' }).as('postRegulation') + + cy.get('[data-cy="Reg. MEMN"]').eq(0).click() + cy.get('[title="Ouest Cotentin Bivalves"]').trigger('mouseover', { force: true }) + cy.get('[data-cy="regulatory-topic-edit"]').should('be.visible') + cy.get('[data-cy="regulatory-topic-edit"]').click({ force: true }) + cy.get('[data-cy="regulatory-topic-edit-input"]').should('exist') + cy.get('[data-cy="regulatory-topic-edit-input"]').should('not.be.disabled') + cy.get('[data-cy="regulatory-topic-edit-input"]').type(' - changed{enter}', { force: true }) + + // When + cy.get('[data-cy="regulatory-topic-edit-input"]').should('not.exist') + + cy.wait('@postRegulation').then(({ request, response }) => { + expect(request.body).contain('typeName="monitorfish:regulations_write"') + expect(request.body).contain('Ouest Cotentin Bivalves - changed') + expect(response && response.statusCode).equal(200) + }) + + // Then + cy.get('[data-cy="regulatory-layers-my-zones-topic"]').then(elements => { + expect(elements.eq(0)).contain('Ouest Cotentin Bivalves - changed') + expect(elements.eq(0).parent()).contain('1/1') + }) + }) +}) diff --git a/frontend/cypress/e2e/backoffice/regulatory_zone_list.spec.ts b/frontend/cypress/e2e/backoffice/regulation_table/table.spec.ts similarity index 56% rename from frontend/cypress/e2e/backoffice/regulatory_zone_list.spec.ts rename to frontend/cypress/e2e/backoffice/regulation_table/table.spec.ts index bdad44be85..b86b1be117 100644 --- a/frontend/cypress/e2e/backoffice/regulatory_zone_list.spec.ts +++ b/frontend/cypress/e2e/backoffice/regulation_table/table.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ -context('Backoffice', () => { +context('BackOffice > Regulation Table > Table', () => { beforeEach(() => { cy.visit('/backoffice/regulation') cy.wait(200) @@ -33,33 +33,4 @@ context('Backoffice', () => { cy.get('[data-cy="regulatory-layer-topic-row"]').eq(0).click({ force: true }) cy.get('[title="Poulpes Mayotte"]').should('exist') }) - - it('on mouse over layer name edit button appears', () => { - // Given - // listen Post request to /geoserver/wfs - cy.intercept('POST', '/geoserver/wfs', { hostname: 'localhost' }).as('postRegulation') - - cy.get('[data-cy="Reg. MEMN"]').eq(0).click() - cy.get('[title="Ouest Cotentin Bivalves"]').trigger('mouseover', { force: true }) - cy.get('[data-cy="regulatory-topic-edit"]').should('be.visible') - cy.get('[data-cy="regulatory-topic-edit"]').click({ force: true }) - cy.get('[data-cy="regulatory-topic-edit-input"]').should('exist') - cy.get('[data-cy="regulatory-topic-edit-input"]').should('not.be.disabled') - cy.get('[data-cy="regulatory-topic-edit-input"]').type(' - changed{enter}', { force: true }) - - // When - cy.get('[data-cy="regulatory-topic-edit-input"]').should('not.exist') - - cy.wait('@postRegulation').then(({ request, response }) => { - expect(request.body).contain('typeName="monitorfish:regulations_write"') - expect(request.body).contain('Ouest Cotentin Bivalves - changed') - expect(response && response.statusCode).equal(200) - }) - - // Then - cy.get('[data-cy="regulatory-layers-my-zones-topic"]').then(elements => { - expect(elements.eq(0)).contain('Ouest Cotentin Bivalves - changed') - expect(elements.eq(0).parent()).contain('1/1') - }) - }) }) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 34f93486a9..d017890c81 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@dnd-kit/core": "6.1.0", "@dnd-kit/modifiers": "6.0.1", - "@mtes-mct/monitor-ui": "24.1.1", + "@mtes-mct/monitor-ui": "24.3.1", "@reduxjs/toolkit": "2.2.7", "@sentry/react": "8.32.0", "@tanstack/react-table": "8.20.5", @@ -2484,16 +2484,16 @@ "license": "BSD-2-Clause" }, "node_modules/@mtes-mct/monitor-ui": { - "version": "24.1.1", - "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-24.1.1.tgz", - "integrity": "sha512-UiDqB0qMU3N7I0gsCd9/k2+di++8MStf8UFOAg0q+mCC3SutG26tVQxP2/M6uer24+Io9m/2IJVvtonE1sn9zg==", + "version": "24.3.1", + "resolved": "https://registry.npmjs.org/@mtes-mct/monitor-ui/-/monitor-ui-24.3.1.tgz", + "integrity": "sha512-pMpqe721TFFP1f8PNC/2/Vsy/1RzfW9dtFsx4FEGZgdm8fFOMY5bhs5dohZU+6BuPQUiWRt24z1nZC8zrADvxw==", "license": "AGPL-3.0", "dependencies": { "@babel/runtime": "7.25.7", "@tanstack/react-table": "8.20.5", "@tanstack/react-virtual": "beta", "prop-types": "15.8.1", - "tslib": "2.7.0" + "tslib": "2.8.0" }, "engines": { "node": ">=20" @@ -17029,9 +17029,9 @@ } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", "license": "0BSD" }, "node_modules/tsutils": { diff --git a/frontend/package.json b/frontend/package.json index c438869f13..4126776075 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,7 +37,7 @@ "dependencies": { "@dnd-kit/core": "6.1.0", "@dnd-kit/modifiers": "6.0.1", - "@mtes-mct/monitor-ui": "24.1.1", + "@mtes-mct/monitor-ui": "24.3.1", "@reduxjs/toolkit": "2.2.7", "@sentry/react": "8.32.0", "@tanstack/react-table": "8.20.5", diff --git a/frontend/src/api/BackendApi.types.ts b/frontend/src/api/BackendApi.types.ts index ada6add61f..6ceed24200 100644 --- a/frontend/src/api/BackendApi.types.ts +++ b/frontend/src/api/BackendApi.types.ts @@ -60,6 +60,15 @@ export namespace BackendApi { /** Thrown when attempting to archive an entity linked to non-archived children. */ UNARCHIVED_CHILD = 'UNARCHIVED_CHILD' } + + export enum ResponseBodyStatusEnum { + FOUND = 'FOUND', + NO_CONTENT = 'NO_CONTENT' + } + + export type ResponseBodyStatus = { + status: ResponseBodyStatusEnum + } } export interface Meta { diff --git a/frontend/src/components/ConfirmationModal.tsx b/frontend/src/components/ConfirmationModal.tsx index 157d9a8adf..7aa83487c8 100644 --- a/frontend/src/components/ConfirmationModal.tsx +++ b/frontend/src/components/ConfirmationModal.tsx @@ -33,7 +33,7 @@ export function ConfirmationModal({ )} - {message} + {typeof message === 'string' ? {message} : message} @@ -48,9 +48,25 @@ export function ConfirmationModal({ // TODO Allow direct `width` prop control in MUI. // This is a mess. I wonder if we should add inner classes in MUI. const StyledDialog = styled(Dialog)` + ${Dialog.Title} { + font-size: 23px; + } + + ${Dialog.Body} { + b, + p { + font-size: 16px; + line-height: 22px; + } + + > p:not(:first-child) { + margin-top: 24px; + } + } + > div:last-child { - max-width: 440px; - min-width: 440px; + max-width: 600px; + min-width: 600px; /* Dialog.Body */ > div:nth-child(2) { diff --git a/frontend/src/features/BackOffice/components/BackOfficeIconLink.tsx b/frontend/src/features/BackOffice/components/BackOfficeIconLink.tsx new file mode 100644 index 0000000000..4d8c9a6f45 --- /dev/null +++ b/frontend/src/features/BackOffice/components/BackOfficeIconLink.tsx @@ -0,0 +1,17 @@ +import { IconButton, type IconButtonProps } from '@mtes-mct/monitor-ui' +import { useCallback } from 'react' +import { useNavigate } from 'react-router-dom' + +export type BackOfficeIconLinkProps = Omit & { + to: string +} +export function BackOfficeIconLink({ to, ...originalProps }: BackOfficeIconLinkProps) { + const navigate = useNavigate() + + const handleClick = useCallback(() => { + navigate(to) + }, [navigate, to]) + + // eslint-disable-next-line react/jsx-props-no-spreading + return +} diff --git a/frontend/src/features/BackOffice/components/BackOfficeSubtitle.tsx b/frontend/src/features/BackOffice/components/BackOfficeSubtitle.tsx new file mode 100644 index 0000000000..b9258c3a75 --- /dev/null +++ b/frontend/src/features/BackOffice/components/BackOfficeSubtitle.tsx @@ -0,0 +1,10 @@ +import styled from 'styled-components' + +export const BackOfficeSubtitle = styled.h2<{ + $isFirst?: boolean + $withSmallBottomMargin?: boolean +}>` + font-size: 18px; + line-height: 1; + margin: ${p => (p.$isFirst ? 0 : 24)}px 0 ${p => (p.$withSmallBottomMargin ? 8 : 24)}px; +` diff --git a/frontend/src/features/BackOffice/components/BackOfficeTitle.tsx b/frontend/src/features/BackOffice/components/BackOfficeTitle.tsx index eb4340b1f9..d30ca192e4 100644 --- a/frontend/src/features/BackOffice/components/BackOfficeTitle.tsx +++ b/frontend/src/features/BackOffice/components/BackOfficeTitle.tsx @@ -1,14 +1,7 @@ import styled from 'styled-components' -export const BackOfficeTitle = styled.h2` - border-bottom: 2px solid ${p => p.theme.color.lightGray}; - color: #282f3e; - font-size: 16px; - font-weight: 700; +export const BackOfficeTitle = styled.h1` + font-size: 24px; line-height: 1; - margin-bottom: 12px; - padding-bottom: 5px; - text-align: left; - text-transform: uppercase; - width: fit-content; + margin: 0 0 24px; ` diff --git a/frontend/src/features/BackOffice/components/BackofficeBody.tsx b/frontend/src/features/BackOffice/components/BackofficeBody.tsx new file mode 100644 index 0000000000..2df1bc1a84 --- /dev/null +++ b/frontend/src/features/BackOffice/components/BackofficeBody.tsx @@ -0,0 +1,15 @@ +import styled from 'styled-components' + +export const BackOfficeBody = styled.div` + box-sizing: border-box; + display: flex; + flex-direction: column; + flex-grow: 1; + height: 100%; + padding: 24px; + overflow-y: auto; + + * { + box-sizing: border-box; + } +` diff --git a/frontend/src/features/BackOffice/components/BackofficeMenu/constants.ts b/frontend/src/features/BackOffice/components/BackofficeMenu/constants.ts index 5008366d98..3daa5efc71 100644 --- a/frontend/src/features/BackOffice/components/BackofficeMenu/constants.ts +++ b/frontend/src/features/BackOffice/components/BackofficeMenu/constants.ts @@ -1,17 +1,20 @@ export enum BackOfficeMenuKey { CONTROL_OBJECTIVE_LIST = 'CONTROL_OBJECTIVE_LIST', FLEET_SEGMENT_LIST = 'CONTROL_UNIT_LIST', + PRIOR_NOTIFICATION_SUBSCRIBER_LIST = 'PRIOR_NOTIFICATION_SUBSCRIBER_LIST', REGULATORY_ZONE_LIST = 'REGULATORY_ZONE_LIST' } export const BACK_OFFICE_MENU_LABEL: Record = { [BackOfficeMenuKey.CONTROL_OBJECTIVE_LIST]: 'Objectifs de contrôle', [BackOfficeMenuKey.FLEET_SEGMENT_LIST]: 'Segments de flotte', + [BackOfficeMenuKey.PRIOR_NOTIFICATION_SUBSCRIBER_LIST]: 'Diffusion des préavis', [BackOfficeMenuKey.REGULATORY_ZONE_LIST]: 'Zones réglementaires' } export const BACK_OFFICE_MENU_PATH: Record = { [BackOfficeMenuKey.CONTROL_OBJECTIVE_LIST]: '/control_objectives', [BackOfficeMenuKey.FLEET_SEGMENT_LIST]: '/fleet_segments', + [BackOfficeMenuKey.PRIOR_NOTIFICATION_SUBSCRIBER_LIST]: '/prior_notification_subscribers', [BackOfficeMenuKey.REGULATORY_ZONE_LIST]: '/regulation' } diff --git a/frontend/src/features/BackOffice/components/BackofficeMenu/index.tsx b/frontend/src/features/BackOffice/components/BackofficeMenu/index.tsx index 1d5e427152..18f6b53097 100644 --- a/frontend/src/features/BackOffice/components/BackofficeMenu/index.tsx +++ b/frontend/src/features/BackOffice/components/BackofficeMenu/index.tsx @@ -19,6 +19,10 @@ export function BackOfficeMenu() { {BACK_OFFICE_MENU_LABEL[BackOfficeMenuKey.FLEET_SEGMENT_LIST]} + + + {BACK_OFFICE_MENU_LABEL[BackOfficeMenuKey.PRIOR_NOTIFICATION_SUBSCRIBER_LIST]} + ) } @@ -31,18 +35,17 @@ const Wrapper = styled.div` flex-direction: column; letter-spacing: 0.5px; line-height: 1; - min-width: 130px; + min-width: 200px; padding: 16px 24px; + width: 200px; ` const StyledNavLink = styled(NavLink)` align-items: center; color: ${p => p.theme.color.gainsboro}; display: flex; - height: 55px; + height: 45px; text-align: left; - line-height: 17px; - width: 140px; && { color: ${p => p.theme.color.gainsboro}; diff --git a/frontend/src/features/PriorNotification/PriorNotificationSubscriber.types.ts b/frontend/src/features/PriorNotification/PriorNotificationSubscriber.types.ts new file mode 100644 index 0000000000..d7cf1adeb5 --- /dev/null +++ b/frontend/src/features/PriorNotification/PriorNotificationSubscriber.types.ts @@ -0,0 +1,57 @@ +export namespace PriorNotificationSubscriber { + export interface Subscriber { + controlUnit: ControlUnit + fleetSegmentSubscriptions: FleetSegmentSubscription[] + portSubscriptions: PortSubscription[] + vesselSubscriptions: VesselSubscription[] + } + + /** @internal Only used within this namespace. */ + type Administration = { + id: number + name: string + } + + /** @internal Only used within this namespace. */ + type ControlUnit = { + administration: Administration + id: number + name: string + } + + export type FleetSegmentSubscription = { + controlUnitId: number + hasSubscribedToAllPriorNotifications: boolean + segmentCode: string + segmentName: string | undefined + } + + export type PortSubscription = { + controlUnitId: number + hasSubscribedToAllPriorNotifications: boolean + portLocode: string + portName: string | undefined + } + + export type VesselSubscription = { + controlUnitId: number + vesselCallSign: string | undefined + vesselCfr: string | undefined + vesselExternalMarking: string | undefined + vesselId: number + vesselMmsi: string | undefined + vesselName: string | undefined + } + + export type FormData = { + controlUnitId: number + fleetSegmentCodes: string[] + portLocodes: string[] + portLocodesWithFullSubscription: string[] + vesselIds: number[] + } + + export enum ApiSortColumn { + CONTROL_UNIT_NAME = 'CONTROL_UNIT_NAME' + } +} diff --git a/frontend/src/features/PriorNotification/backoffice.slice.ts b/frontend/src/features/PriorNotification/backoffice.slice.ts new file mode 100644 index 0000000000..657ba338b1 --- /dev/null +++ b/frontend/src/features/PriorNotification/backoffice.slice.ts @@ -0,0 +1,28 @@ +import { createSlice, type PayloadAction } from '@reduxjs/toolkit' + +import { DEFAULT_TABLE_FILTER_VALUES } from './components/PriorNotificationSubscriberTable/constants' + +import type { TableFilter } from './components/PriorNotificationSubscriberTable/types' + +export interface BackofficePriorNotificationState { + tableFilterValues: TableFilter +} +const INITIAL_STATE: BackofficePriorNotificationState = { + tableFilterValues: DEFAULT_TABLE_FILTER_VALUES +} + +const backofficePriorNotificationSlice = createSlice({ + initialState: INITIAL_STATE, + name: 'priorNotification', + reducers: { + setTableFilterValues(state, action: PayloadAction>) { + state.tableFilterValues = { + ...state.tableFilterValues, + ...action.payload + } + } + } +}) + +export const backofficePriorNotificationActions = backofficePriorNotificationSlice.actions +export const backofficePriorNotificationReducer = backofficePriorNotificationSlice.reducer diff --git a/frontend/src/features/PriorNotification/components/PriorNotificationSubscriberForm/AllPortSubscriptionsField.tsx b/frontend/src/features/PriorNotification/components/PriorNotificationSubscriberForm/AllPortSubscriptionsField.tsx new file mode 100644 index 0000000000..3f1825a12e --- /dev/null +++ b/frontend/src/features/PriorNotification/components/PriorNotificationSubscriberForm/AllPortSubscriptionsField.tsx @@ -0,0 +1,69 @@ +import { BackOfficeSubtitle } from '@features/BackOffice/components/BackOfficeSubtitle' +import { useGetPortsAsOptions } from '@hooks/useGetPortsAsOptions' +import { DataTable, Select } from '@mtes-mct/monitor-ui' + +import { Info } from './shared/Info' +import { getPortSubscriptionTableColumns } from './utils' + +import type { PriorNotificationSubscriber } from '@features/PriorNotification/PriorNotificationSubscriber.types' +import type { Promisable } from 'type-fest' + +type AllPortSubscriptionsFieldProps = Readonly<{ + isDisabled: boolean + onAdd: (newPortLocode: string, isFullPortSubscription: boolean) => Promisable + onRemove: (portLocodeToRemove: string, isFullPortSubscription: boolean) => Promisable + portSubscriptions: PriorNotificationSubscriber.PortSubscription[] +}> +export function AllPortSubscriptionsField({ + isDisabled, + onAdd, + onRemove, + portSubscriptions +}: AllPortSubscriptionsFieldProps) { + const { portsAsOptions } = useGetPortsAsOptions() + + const add = (newPortLocode: string | undefined) => { + if (!newPortLocode) { + return + } + + onAdd(newPortLocode, false) + } + + const remove = (portLocodeToRemove: string) => { + onRemove(portLocodeToRemove, false) + } + + const columns = getPortSubscriptionTableColumns(remove, false, isDisabled) + + return ( + <> + + Ports de diffusion de base de l’unité + + + Les préavis de ces ports seront diffusés si les navires ont une note de risque égale ou supérieure à 2,3. Ils + seront vérifiés par le CNSP avant leur diffusion. + + + `${originalRow.controlUnitId}-${originalRow.portLocode}` + }} + withoutHead + /> + + + + {unsubscriptionConfirmationModalPortLocode && ( + +

+ Êtes-vous sûr de vouloir supprimer ce port de diffusion ? +

+

+ L’unité ne recevra plus les préavis des navires ayant une note de risque supérieure à 2,3 lorsqu’ils + débarquent dans ce port. +

+ + } + onCancel={closeRemovalConfirmationModal} + onConfirm={remove} + title="Supprimer un port de diffusion" + /> + )} + + ) +} diff --git a/frontend/src/features/PriorNotification/components/PriorNotificationSubscriberForm/SegmentSubscriptionsField.tsx b/frontend/src/features/PriorNotification/components/PriorNotificationSubscriberForm/SegmentSubscriptionsField.tsx new file mode 100644 index 0000000000..8906338eda --- /dev/null +++ b/frontend/src/features/PriorNotification/components/PriorNotificationSubscriberForm/SegmentSubscriptionsField.tsx @@ -0,0 +1,69 @@ +import { BackOfficeSubtitle } from '@features/BackOffice/components/BackOfficeSubtitle' +import { useGetFleetSegmentsAsOptions } from '@features/FleetSegment/hooks/useGetFleetSegmentsAsOptions' +import { DataTable, Select } from '@mtes-mct/monitor-ui' + +import { Info } from './shared/Info' +import { getSegmentSubscriptionTableColumns } from './utils' + +import type { PriorNotificationSubscriber } from '@features/PriorNotification/PriorNotificationSubscriber.types' +import type { Promisable } from 'type-fest' + +type SegmentSubscriptionsFieldProps = Readonly<{ + isDisabled: boolean + onAdd: (newSegmentCode: string) => Promisable + onRemove: (segmentCodeToRemove: string) => Promisable + segmentSubscriptions: PriorNotificationSubscriber.FleetSegmentSubscription[] +}> +export function SegmentSubscriptionsField({ + isDisabled, + onAdd, + onRemove, + segmentSubscriptions +}: SegmentSubscriptionsFieldProps) { + const { fleetSegmentsAsOptions } = useGetFleetSegmentsAsOptions() + + const add = (newSegmentCode: string | undefined) => { + if (!newSegmentCode) { + return + } + + onAdd(newSegmentCode) + } + + const remove = (segmentCodeToRemove: string) => { + onRemove(segmentCodeToRemove) + } + + const columns = getSegmentSubscriptionTableColumns(remove, isDisabled) + + return ( + <> + + Ajouter tous les préavis d’un segment à la diffusion + + + Tous les préavis des navires appartenant à ces segments seront diffusés, sans faire partie du périmètre de + vérification du CNSP. + + + `${originalRow.controlUnitId}-${originalRow.segmentCode}` + }} + withoutHead + /> + + + )} + + {portsAsOptions && ( + + {vesselName && isLinkToVesselSidebarDisplayed && ( + <> + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + + Voir la fiche + + + )} + {vesselName && ( + + )} + + + + ) +} + +const Wrapper = styled.div<{ + $extendedWidth: number | undefined + $isExtended: boolean +}>` + box-sizing: border-box; + width: ${p => (p.$isExtended && p.$extendedWidth !== undefined ? p.$extendedWidth : 320)}px; + transition: all 0.7s; + + * { + box-sizing: border-box; + } +` + +const Input = styled.input<{ + $baseUrl: string + $flagState: string | undefined + $hasError: boolean | undefined +}>` + margin: 0; + border: ${p => (p.$hasError ? '1px solid red' : 'none')}; + border-radius: 0; + border-radius: 2px; + color: ${p => p.theme.color.gunMetal}; + font-size: 13px; + font-weight: 500; + height: 40px; + width: 100%; + padding: 0 5px 0 10px; + flex: 3; + transition: all 0.7s; + background: ${p => + p.$flagState ? `url(${p.$baseUrl}/flags/${p.$flagState.toLowerCase()}.svg) no-repeat scroll, white` : 'white'}; + background-size: 20px; + background-position-y: center; + background-position-x: 16px; + padding-left: ${p => (p.$flagState ? 45 : 16)}px; + + &:disabled { + background-color: var(--rs-input-disabled-bg); + } + + &:hover, + &:focus { + border: none; + } +` + +const InputWrapper = styled.div` + position: relative; + + /* Clear icon button */ + > button { + position: absolute; + right: 7.5px; + top: 7.5px; + } + + /* Open vessel sidebar link */ + > a { + position: absolute; + right: 42px; + top: 11px; + cursor: pointer; + } +` diff --git a/frontend/src/features/Vessel/components/VesselSearch/utils.ts b/frontend/src/features/Vessel/components/VesselSearch/utils.ts new file mode 100644 index 0000000000..da6d271468 --- /dev/null +++ b/frontend/src/features/Vessel/components/VesselSearch/utils.ts @@ -0,0 +1,50 @@ +import { VesselIdentifier } from '../../../../domain/entities/vessel/types' + +import type { VesselIdentity } from '../../../../domain/entities/vessel/types' +import type { Vessel } from '@features/Vessel/Vessel.types' + +/** + * Remove duplicated vessels : keep vessels from APIs when a duplicate is found on either + * - internalReferenceNumber (CFR) or + * - vesselId (Vessel internal identifier) + */ +export function removeDuplicatedFoundVessels( + foundVesselsFromAPI: VesselIdentity[], + foundVesselsOnMap: VesselIdentity[] +): VesselIdentity[] { + const filteredVesselsFromMap = foundVesselsOnMap.filter(vesselFromMap => { + if (!vesselFromMap.internalReferenceNumber) { + return true + } + + return !foundVesselsFromAPI.some( + vesselFromApi => + vesselFromApi.internalReferenceNumber === vesselFromMap.internalReferenceNumber || + (vesselFromApi.vesselId && vesselFromApi.vesselId === vesselFromMap.vesselId) + ) + }) + + return foundVesselsFromAPI.concat(filteredVesselsFromMap).slice(0, 50) +} + +export function enrichWithVesselIdentifierIfNotFound( + identity: Vessel.VesselEnhancedObject | VesselIdentity +): VesselIdentity { + if (identity.vesselIdentifier) { + return identity + } + + if (identity.internalReferenceNumber) { + return { ...identity, vesselIdentifier: VesselIdentifier.INTERNAL_REFERENCE_NUMBER } + } + + if (identity.ircs) { + return { ...identity, vesselIdentifier: VesselIdentifier.IRCS } + } + + if (identity.externalReferenceNumber) { + return { ...identity, vesselIdentifier: VesselIdentifier.EXTERNAL_REFERENCE_NUMBER } + } + + return identity +} diff --git a/frontend/src/features/VesselSearch/index.tsx b/frontend/src/features/VesselSearch/index.tsx index 6641698359..19c228bf94 100644 --- a/frontend/src/features/VesselSearch/index.tsx +++ b/frontend/src/features/VesselSearch/index.tsx @@ -33,6 +33,7 @@ type VesselSearchProps = Omit, 'defaultVal onClickOutsideOrEscape?: () => Promisable onInputClick?: () => Promisable } +/** @deprecated Use `@features/Vessel/components/VesselSearch` instead. */ export function VesselSearch({ baseRef, className, diff --git a/frontend/src/hooks/useGetAdministrationsAsOptions.ts b/frontend/src/hooks/useGetAdministrationsAsOptions.ts new file mode 100644 index 0000000000..3fe0792f19 --- /dev/null +++ b/frontend/src/hooks/useGetAdministrationsAsOptions.ts @@ -0,0 +1,35 @@ +import { RTK_FIVE_MINUTES_POLLING_QUERY_OPTIONS } from '@api/constants' +import { useGetAdministrationsQuery } from '@features/ControlUnit/administrationApi' +import { useMemo } from 'react' + +import type { Option } from '@mtes-mct/monitor-ui' + +/** + * Fetches administrations and returns them as options with their `id` property as option value. + */ +export function useGetAdministrationsAsOptions() { + const { + data: administrations, + error, + isLoading + } = useGetAdministrationsQuery(undefined, RTK_FIVE_MINUTES_POLLING_QUERY_OPTIONS) + + const administrationsAsOptions: Option[] | undefined = useMemo(() => { + if (!administrations) { + return undefined + } + + return administrations + .map(administration => ({ + label: administration.name, + value: administration.id + })) + .sort((a, b) => a.label.localeCompare(b.label)) + }, [administrations]) + + return { + administrationsAsOptions, + error, + isLoading + } +} diff --git a/frontend/src/hooks/useGetControlUnitsAsOptions.ts b/frontend/src/hooks/useGetControlUnitsAsOptions.ts new file mode 100644 index 0000000000..aa532a372c --- /dev/null +++ b/frontend/src/hooks/useGetControlUnitsAsOptions.ts @@ -0,0 +1,35 @@ +import { RTK_FIVE_MINUTES_POLLING_QUERY_OPTIONS } from '@api/constants' +import { useGetControlUnitsQuery } from '@features/ControlUnit/controlUnitApi' +import { useMemo } from 'react' + +import type { Option } from '@mtes-mct/monitor-ui' + +/** + * Fetches control units and returns them as options with their `id` property as option value. + */ +export function useGetControlUnitsAsOptions() { + const { + data: controlUnits, + error, + isLoading + } = useGetControlUnitsQuery(undefined, RTK_FIVE_MINUTES_POLLING_QUERY_OPTIONS) + + const administrationsAsOptions: Option[] | undefined = useMemo(() => { + if (!controlUnits) { + return undefined + } + + return controlUnits + .map(controlUnit => ({ + label: controlUnit.name, + value: controlUnit.id + })) + .sort((a, b) => a.label.localeCompare(b.label)) + }, [controlUnits]) + + return { + administrationsAsOptions, + error, + isLoading + } +} diff --git a/frontend/src/pages/BackofficePage.tsx b/frontend/src/pages/BackofficePage.tsx index f5eec0804b..2462f7ac1d 100644 --- a/frontend/src/pages/BackofficePage.tsx +++ b/frontend/src/pages/BackofficePage.tsx @@ -5,7 +5,6 @@ import { Provider } from 'react-redux' import { Outlet } from 'react-router-dom' import { PersistGate } from 'redux-persist/integration/react' import styled from 'styled-components' -import { LegacyRsuiteComponentsWrapper } from 'ui/LegacyRsuiteComponentsWrapper' import { NamespaceContext } from '../context/NamespaceContext' import { LayerSliceNamespace } from '../domain/entities/layers/types' @@ -23,13 +22,11 @@ export function BackofficePage() { - - - + + - - - + + @@ -38,11 +35,7 @@ export function BackofficePage() { ) } -const BackofficeWrapper = styled.div` - box-sizing: border-box; +const Wrapper = styled.div` display: flex; - font-size: 13px; height: 100%; - overflow: hidden; - width: 100%; ` diff --git a/frontend/src/paths.ts b/frontend/src/paths.ts index 0541365a25..559b2e2a4e 100644 --- a/frontend/src/paths.ts +++ b/frontend/src/paths.ts @@ -2,6 +2,7 @@ export const paths = { backendForFrontend: '/bff', backoffice: '/backoffice', controlObjectives: 'control_objectives', + editPpriorNotificationSubscriber: 'prior_notification_subscribers/:controlUnitId', editRegulation: 'regulation/edit', ext: '/ext', fleetSegments: 'fleet_segments', @@ -10,6 +11,7 @@ export const paths = { loadLight: '/load_light', login: '/login', newRegulation: 'regulation/new', + priorNotificationSubscribers: 'prior_notification_subscribers', register: '/register', regulation: 'regulation', sideWindow: '/side_window' diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 3fabea9342..df60c9ea04 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -3,6 +3,8 @@ import { EditRegulation } from '@features/BackOffice/edit_regulation/EditRegulat import { ControlObjectiveTable } from '@features/ControlObjective/components/ControlObjectiveTable' import { FleetSegmentsBackoffice } from '@features/FleetSegment/components/FleetSegmentsBackoffice' import { MainWindow } from '@features/MainWindow' +import { PriorNotificationSubscriberForm } from '@features/PriorNotification/components/PriorNotificationSubscriberForm' +import { PriorNotificationSubscriberTable } from '@features/PriorNotification/components/PriorNotificationSubscriberTable' import { SideWindow } from '@features/SideWindow' import { BackofficePage } from '@pages/BackofficePage' import { HomePage } from '@pages/HomePage' @@ -115,6 +117,22 @@ export const routes = [ ) + }, + { + path: paths.priorNotificationSubscribers, + element: ( + + + + ) + }, + { + path: paths.editPpriorNotificationSubscriber, + element: ( + + + + ) } ] }, diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index 16c721e4dc..f22fcf2081 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -98,7 +98,7 @@ export const backofficeStore = configureStore({ // TODO Replace all Redux state Dates by strings & Error by a strict-typed POJO. serializableCheck: false } - }).concat(monitorfishApi.middleware), + }).concat(monitorenvApi.middleware, monitorfishApi.middleware), reducer: persistedBackofficeReducer }) diff --git a/frontend/src/store/reducers.ts b/frontend/src/store/reducers.ts index 30effc0270..15333495a7 100644 --- a/frontend/src/store/reducers.ts +++ b/frontend/src/store/reducers.ts @@ -10,6 +10,7 @@ import { mainWindowReducer } from '@features/MainWindow/slice' import { measurementReducer, type MeasurementState } from '@features/Measurement/slice' import { missionFormReducer } from '@features/Mission/components/MissionForm/slice' import { missionListReducer, type MissionListState } from '@features/Mission/components/MissionList/slice' +import { backofficePriorNotificationReducer } from '@features/PriorNotification/backoffice.slice' import { priorNotificationReducer, type PriorNotificationState } from '@features/PriorNotification/slice' import { regulatoryLayerSearchReducer } from '@features/Regulation/components/RegulationSearch/slice' import { regulatoryReducer } from '@features/Regulation/slice' @@ -52,6 +53,7 @@ const getCommonPersistReducerConfig = (key: string, whitelist?: Array