From e25e175283d070d3ea4099522a8eb7f664bdbad7 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 24 May 2024 17:56:45 +0200 Subject: [PATCH 1/9] Fix is_coastal type casting in beacons flow --- datascience/src/pipeline/flows/beacons.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/datascience/src/pipeline/flows/beacons.py b/datascience/src/pipeline/flows/beacons.py index f988a8ee30..dab0014b81 100644 --- a/datascience/src/pipeline/flows/beacons.py +++ b/datascience/src/pipeline/flows/beacons.py @@ -1,6 +1,5 @@ from pathlib import Path -import numpy as np import pandas as pd import prefect from prefect import Flow, case, task @@ -8,6 +7,7 @@ from src.pipeline.entities.beacon_malfunctions import BeaconStatus from src.pipeline.generic_tasks import extract, load +from src.pipeline.processing import zeros_ones_to_bools from src.pipeline.shared_tasks.control_flow import check_flow_not_running @@ -43,9 +43,7 @@ def transform_beacons(beacons: pd.DataFrame) -> pd.DataFrame: BeaconStatus.from_poseidon_status, na_action="ignore" ).map(lambda beacon_status: beacon_status.value, na_action="ignore") - beacons["is_coastal"] = beacons.is_coastal.map( - {1.0: True, 0.0: False, np.nan: None} - ) + beacons["is_coastal"] = zeros_ones_to_bools(beacons.is_coastal) return beacons @@ -61,7 +59,6 @@ def transform_satellite_operators(satellite_operators: pd.DataFrame) -> pd.DataF @task(checkpoint=False) def load_beacons(beacons): - load( beacons, table_name="beacons", @@ -87,10 +84,8 @@ def load_satellite_operators(satellite_operators): with Flow("Beacons", executor=LocalDaskExecutor()) as flow: - flow_not_running = check_flow_not_running() with case(flow_not_running, True): - # Extract beacons = extract_beacons() satellite_operators = extract_satellite_operators() From 03420bc7a6bfe57dee77d517779d93fc803d7b42 Mon Sep 17 00:00:00 2001 From: Vincent Date: Mon, 27 May 2024 09:20:39 +0200 Subject: [PATCH 2/9] Add logbook equipment data to vessels --- .../internal/V0.254__Update_vessels_table.sql | 3 ++ datascience/src/pipeline/flows/vessels.py | 29 +++++++++++++++--- .../queries/fmc/vessels_logbook_equipment.sql | 10 +++++++ .../test_pipeline/test_flows/test_vessels.py | 30 +++++++++++++++++-- 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 backend/src/main/resources/db/migration/internal/V0.254__Update_vessels_table.sql create mode 100644 datascience/src/pipeline/queries/fmc/vessels_logbook_equipment.sql diff --git a/backend/src/main/resources/db/migration/internal/V0.254__Update_vessels_table.sql b/backend/src/main/resources/db/migration/internal/V0.254__Update_vessels_table.sql new file mode 100644 index 0000000000..8646d9665c --- /dev/null +++ b/backend/src/main/resources/db/migration/internal/V0.254__Update_vessels_table.sql @@ -0,0 +1,3 @@ +ALTER TABLE public.vessels + ADD COLUMN logbook_equipment_status VARCHAR, + ADD COLUMN has_esacapt BOOLEAN NOT NULL DEFAULT false; \ No newline at end of file diff --git a/datascience/src/pipeline/flows/vessels.py b/datascience/src/pipeline/flows/vessels.py index ad13841c5a..d61a62c1ad 100644 --- a/datascience/src/pipeline/flows/vessels.py +++ b/datascience/src/pipeline/flows/vessels.py @@ -7,7 +7,7 @@ from config import UNKNOWN_VESSEL_ID from src.pipeline.generic_tasks import extract, load -from src.pipeline.processing import coalesce, concatenate_columns +from src.pipeline.processing import coalesce, concatenate_columns, zeros_ones_to_bools from src.pipeline.shared_tasks.control_flow import check_flow_not_running @@ -56,6 +56,17 @@ def extract_vessels_operators() -> pd.DataFrame: return extract("fmc", "fmc/vessels_operators.sql") +@task(checkpoint=False) +def extract_vessels_logbook_equipement() -> pd.DataFrame: + """ + Extracts vessels logbook equipement data + + Returns: + pd.DataFrame: Vessels logbook equipment data + """ + return extract("fmc", "fmc/vessels_logbook_equipment.sql") + + @task(checkpoint=False) def extract_french_vessels_navigation_licences() -> pd.DataFrame: """ @@ -85,6 +96,7 @@ def concat_merge_vessels( french_vessels: pd.DataFrame, eu_vessels: pd.DataFrame, non_eu_vessels: pd.DataFrame, + vessels_logbook_equipement: pd.DataFrame, vessels_operators: pd.DataFrame, licences: pd.DataFrame, control_charters: pd.DataFrame, @@ -107,6 +119,7 @@ def concat_merge_vessels( french_vessels (pd.DataFrame): French vessels eu_vessels (pd.DataFrame): EU vessels non_eu_vessels (pd.DataFrame): non-EU vessels + vessels_logbook_equipement (pd.DataFrame): vessels logbook equipment data vessels_operators (pd.DataFrame): vessels' operators data licences (pd.DataFrame): french vessels navigation licences data control_charters (pd.DataFrame): vessels under_charter status @@ -119,6 +132,8 @@ def concat_merge_vessels( """ all_vessels = pd.concat([french_vessels, eu_vessels, non_eu_vessels]) + all_vessels = pd.merge(all_vessels, vessels_logbook_equipement, on="id", how="left") + all_vessels = pd.merge(all_vessels, vessels_operators, on="id", how="left") all_vessels = pd.merge(all_vessels, licences, on="id", how="left") @@ -161,6 +176,7 @@ def concat_merge_vessels( "fishing_gear_main": "category", "fishing_gear_secondary": "category", "fishing_gear_third": "category", + "logbook_equipment_status": "category", "operator_email_1": "category", "operator_email_2": "category", "operator_phone_1": "category", @@ -272,6 +288,10 @@ def clean_vessels(all_vessels: pd.DataFrame) -> pd.DataFrame: res.loc[:, col_name] = coalesce(res[cols_list]) logger.info("Columns combined into single values.") + # Data conversions + logger.info("Converting data...") + res["has_esacapt"] = zeros_ones_to_bools(res.has_esacapt).fillna(False) + # Sort columns logger.info("Sorting columns...") @@ -310,6 +330,8 @@ def clean_vessels(all_vessels: pd.DataFrame) -> pd.DataFrame: "vessel_fax", "vessel_telex", "under_charter", + "logbook_equipment_status", + "has_esacapt", ] res = res[columns] logger.info("Columns sorted.") @@ -337,7 +359,6 @@ def add_unknown_vessel(all_vessels: pd.DataFrame) -> pd.DataFrame: try: assert UNKNOWN_VESSEL_ID not in all_vessels.id.values except AssertionError: - logger = prefect.context.get("logger") logger.error( f"Reserved unkwnown vessel id {UNKNOWN_VESSEL_ID} " @@ -389,10 +410,8 @@ def load_vessels(all_vessels: pd.DataFrame): with Flow("Vessels", executor=LocalDaskExecutor()) as flow: - flow_not_running = check_flow_not_running() with case(flow_not_running, True): - # Extract french_vessels = extract_french_vessels() eu_vessels = extract_eu_vessels() @@ -400,12 +419,14 @@ def load_vessels(all_vessels: pd.DataFrame): vessels_operators = extract_vessels_operators() licences = extract_french_vessels_navigation_licences() control_charters = extract_control_charters() + vessels_logbook_equipement = extract_vessels_logbook_equipement() # Transform all_vessels = concat_merge_vessels( french_vessels, eu_vessels, non_eu_vessels, + vessels_logbook_equipement, vessels_operators, licences, control_charters, diff --git a/datascience/src/pipeline/queries/fmc/vessels_logbook_equipment.sql b/datascience/src/pipeline/queries/fmc/vessels_logbook_equipment.sql new file mode 100644 index 0000000000..ff175ecfb6 --- /dev/null +++ b/datascience/src/pipeline/queries/fmc/vessels_logbook_equipment.sql @@ -0,0 +1,10 @@ +SELECT + n.id_fmc_navire AS id, + ceq.libelle AS logbook_equipment_status, + eq.equipe_esacapt AS has_esacapt +FROM fmc2.fmc_navire n +LEFT JOIN fmc2.fmc_code_statut_equipemen ceq +ON ceq.idc_fmc_statut_equipemen = n.idc_fmc_statut_equipemen +LEFT JOIN fmc2.fmc_equipement_ers eq +ON eq.id_fmc_navire = n.id_fmc_navire AND eq.est_courant = 1 +WHERE n.est_actif = 1 \ No newline at end of file diff --git a/datascience/tests/test_pipeline/test_flows/test_vessels.py b/datascience/tests/test_pipeline/test_flows/test_vessels.py index 9328eb1eb0..7023886978 100644 --- a/datascience/tests/test_pipeline/test_flows/test_vessels.py +++ b/datascience/tests/test_pipeline/test_flows/test_vessels.py @@ -15,6 +15,7 @@ extract_french_vessels, extract_french_vessels_navigation_licences, extract_non_eu_vessels, + extract_vessels_logbook_equipement, extract_vessels_operators, load_vessels, ) @@ -114,6 +115,13 @@ "fishing_gear_secondary": [None, None], } + +vessels_logbook_equipment_data = { + "id": [1, 2], + "logbook_equipment_status": ["Equipment status 1", None], + "has_esacapt": [1, None], +} + vessels_operators_data = { "id": [1, 3], "operator_name_pos": ["op1 pos", "op3 pos"], @@ -178,6 +186,8 @@ "operator_email_2": [None, None, None, None, "contact_2@email.com", None], "operator_phone_1": [None, None, None, None, "123", "789"], "operator_phone_2": [None, None, None, None, "456", "101112"], + "logbook_equipment_status": ["Equipment status 1", None, None, None, None, None], + "has_esacapt": [1, None, None, None, None, None], "operator_name_pos": ["op1 pos", None, "op3 pos", None, None, None], "operator_email_pos": [None, None, "operator_pos@mail", None, None, None], "operator_phone_1_pos": ["pos_01", None, None, None, None, None], @@ -215,6 +225,7 @@ "registry_port": "category", "sailing_category": "category", "sailing_type": "category", + "logbook_equipment_status": "category", "operator_email": "category", "operator_phone": "category", "operator_mobile_phone": "category", @@ -317,6 +328,8 @@ "vessel_fax": [None, "159753", None, None, None, None], "vessel_telex": [None, "555", None, None, None, None], "under_charter": [False, False, False, True, False, False], + "logbook_equipment_status": ["Equipment status 1", None, None, None, None, None], + "has_esacapt": [True, False, False, False, False, False], } cleaned_vessels_dtype = { @@ -333,6 +346,7 @@ "sailing_category": "category", "sailing_type": "category", "proprietor_name": "category", + "logbook_equipment_status": "category", "under_charter": bool, } @@ -352,6 +366,11 @@ def non_eu_vessels() -> pd.DataFrame: return pd.DataFrame(non_eu_vessels_data) +@pytest.fixture +def vessels_logbook_equipment() -> pd.DataFrame: + return pd.DataFrame(vessels_logbook_equipment_data) + + @pytest.fixture def vessels_operators() -> pd.DataFrame: return pd.DataFrame(vessels_operators_data) @@ -418,6 +437,13 @@ def test_extract_control_charters(reset_test_data, expected_vessels_under_charte pd.testing.assert_frame_equal(vessels_under_charter, expected_vessels_under_charter) +@patch("src.pipeline.flows.vessels.extract") +def test_extract_vessels_logbook_equipement(mock_extract): + mock_extract.side_effect = mock_extract_side_effect + query = extract_vessels_logbook_equipement.run() + assert isinstance(query, sqlalchemy.sql.elements.TextClause) + + @patch("src.pipeline.flows.vessels.extract") def test_extract_vessels_operators(mock_extract): mock_extract.side_effect = mock_extract_side_effect @@ -429,16 +455,17 @@ def test_concat_merge_vessels( french_vessels, eu_vessels, non_eu_vessels, + vessels_logbook_equipment, vessels_operators, licences, control_charters, all_vessels, ): - res = concat_merge_vessels.run( french_vessels, eu_vessels, non_eu_vessels, + vessels_logbook_equipment, vessels_operators, licences, control_charters, @@ -448,7 +475,6 @@ def test_concat_merge_vessels( def test_clean_vessels(all_vessels, cleaned_vessels): - res = clean_vessels.run(all_vessels) pd.testing.assert_frame_equal(res, cleaned_vessels) From 4ca08b8865d3d0aa9b03c9fd9bb240cc3008a290 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Fri, 31 May 2024 11:41:51 +0200 Subject: [PATCH 3/9] Add beacon to vessel APIs --- .../entities/beacon_malfunctions/Beacon.kt | 4 + .../domain/entities/vessel/Vessel.kt | 1 - .../domain/entities/vessel/VesselAndBeacon.kt | 8 + .../entities/vessel/VesselInformation.kt | 2 + .../domain/repositories/BeaconRepository.kt | 2 +- .../domain/use_cases/vessel/GetVessel.kt | 12 +- .../domain/use_cases/vessel/SearchVessels.kt | 25 +- .../api/bff/VesselController.kt | 4 +- .../outputs/VesselAndPositionsDataOutput.kt | 1 + .../api/light/outputs/VesselDataOutput.kt | 8 +- .../api/outputs/BeaconDataOutput.kt | 20 ++ .../outputs/VesselAndPositionsDataOutput.kt | 5 +- .../api/outputs/VesselDataOutput.kt | 9 +- .../api/outputs/VesselIdentityDataOutput.kt | 24 +- .../database/entities/BeaconEntity.kt | 9 + .../repositories/JpaBeaconRepository.kt | 4 +- .../domain/use_cases/GetVesselUTests.kt | 65 ++--- .../domain/use_cases/SearchVesselsUTests.kt | 16 +- .../api/bff/VesselControllerITests.kt | 226 +++++++++++++----- .../api/light/VesselLightControllerITests.kt | 1 + .../repositories/JpaBeaconRepositoryITests.kt | 2 +- 21 files changed, 308 insertions(+), 140 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselAndBeacon.kt create mode 100644 backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/BeaconDataOutput.kt diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/beacon_malfunctions/Beacon.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/beacon_malfunctions/Beacon.kt index cdb7b5dea7..68fcd75c22 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/beacon_malfunctions/Beacon.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/beacon_malfunctions/Beacon.kt @@ -1,8 +1,12 @@ package fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions +import java.time.ZonedDateTime + data class Beacon( val beaconNumber: String, val vesselId: Int?, val beaconStatus: BeaconStatus? = null, val satelliteOperatorId: Int? = null, + val isCoastal: Boolean? = null, + val loggingDatetimeUtc: ZonedDateTime? = null, ) 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 dd6c0de54b..54f329eaa9 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 @@ -38,7 +38,6 @@ data class Vessel( val proprietorEmails: List? = null, val vesselPhones: List? = null, val vesselEmails: List? = null, - val beaconNumber: String? = null, val underCharter: Boolean? = null, ) { fun isLessThanTwelveMetersVessel(): Boolean { diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselAndBeacon.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselAndBeacon.kt new file mode 100644 index 0000000000..3039a65ce9 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselAndBeacon.kt @@ -0,0 +1,8 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.vessel + +import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon + +data class VesselAndBeacon( + val vessel: Vessel, + val beacon: Beacon? = null, +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselInformation.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselInformation.kt index abdda92291..9c681856a4 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselInformation.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselInformation.kt @@ -1,10 +1,12 @@ package fr.gouv.cnsp.monitorfish.domain.entities.vessel +import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon import fr.gouv.cnsp.monitorfish.domain.entities.position.Position import fr.gouv.cnsp.monitorfish.domain.entities.risk_factor.VesselRiskFactor data class VesselInformation( val vessel: Vessel?, + val beacon: Beacon?, val positions: List, val vesselRiskFactor: VesselRiskFactor, ) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/BeaconRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/BeaconRepository.kt index e1a3b3dd0b..35502f8a87 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/BeaconRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/BeaconRepository.kt @@ -4,6 +4,6 @@ import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon interface BeaconRepository { fun search(searched: String): List - fun findBeaconNumberByVesselId(vesselId: Int): String? + fun findBeaconByVesselId(vesselId: Int): Beacon? fun findActivatedBeaconNumbers(): List } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt index 28c2840cb5..baf6cc2768 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt @@ -58,19 +58,13 @@ class GetVessel( } val vessel = vesselFuture.await() - val vesselWithBeaconNumber = - vessel?.id?.let { vesselId -> - val beaconNumber = beaconRepository.findBeaconNumberByVesselId(vesselId) - - beaconNumber?.let { - vessel.copy(beaconNumber = it) - } - } ?: vessel + val beacon = vessel?.id?.let { vesselId -> beaconRepository.findBeaconByVesselId(vesselId) } Pair( vesselTrackHasBeenModified, VesselInformation( - vesselWithBeaconNumber, + vessel, + beacon, positions.await(), vesselRiskFactorsFuture.await() ?: VesselRiskFactor(), ), diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/SearchVessels.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/SearchVessels.kt index 341d573639..9d087eaaac 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/SearchVessels.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/SearchVessels.kt @@ -1,7 +1,7 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.vessel import fr.gouv.cnsp.monitorfish.config.UseCase -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselAndBeacon import fr.gouv.cnsp.monitorfish.domain.repositories.BeaconRepository import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository @@ -10,21 +10,28 @@ class SearchVessels( private val vesselRepository: VesselRepository, private val beaconRepository: BeaconRepository, ) { - fun execute(searched: String): List { + fun execute(searched: String): List { val vessels = vesselRepository.search(searched).filter { !( it.internalReferenceNumber.isNullOrEmpty() && it.externalReferenceNumber.isNullOrEmpty() && it.ircs.isNullOrEmpty() && - it.mmsi.isNullOrEmpty() && - it.beaconNumber.isNullOrEmpty() + it.mmsi.isNullOrEmpty() ) - } + }.map { VesselAndBeacon(vessel = it) } - val vesselIdsFromBeacons = beaconRepository.search(searched).mapNotNull { it.vesselId } - val vesselsFromBeacons = vesselRepository.findVesselsByIds(vesselIdsFromBeacons) + val beacons = beaconRepository.search(searched) + val beaconsVesselId = beacons.mapNotNull { it.vesselId } - return (vessels + vesselsFromBeacons) - .distinctBy { it.id } + val vesselsFromBeacons = vesselRepository + .findVesselsByIds(beaconsVesselId) + .map { vessel -> + val beacon = beacons.find { beacon -> beacon.vesselId == vessel.id } + + return@map VesselAndBeacon(vessel, beacon) + } + + return (vesselsFromBeacons + vessels) + .distinctBy { it.vessel.id } } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselController.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselController.kt index e5f51ee36c..884258e9b4 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselController.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselController.kt @@ -89,7 +89,7 @@ class VesselController( val returnCode = if (vesselTrackHasBeenModified) HttpStatus.ACCEPTED else HttpStatus.OK - ResponseEntity.status(returnCode).body(VesselAndPositionsDataOutput.fromVesselWithData(vesselWithData)) + ResponseEntity.status(returnCode).body(VesselAndPositionsDataOutput.fromVesselInformation(vesselWithData)) } } @@ -213,7 +213,7 @@ class VesselController( searched: String, ): List { return searchVessels.execute(searched).map { - VesselIdentityDataOutput.fromVessel(it) + VesselIdentityDataOutput.fromVesselAndBeacon(it) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselAndPositionsDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselAndPositionsDataOutput.kt index 9d573c8e0e..d2f680769b 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselAndPositionsDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselAndPositionsDataOutput.kt @@ -12,6 +12,7 @@ data class VesselAndPositionsDataOutput( return VesselAndPositionsDataOutput( vessel = VesselDataOutput.fromVessel( vesselInformation.vessel, + vesselInformation.beacon, ), positions = vesselInformation.positions.map { PositionDataOutput.fromPosition(it) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt index b254dff425..7c4274faae 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt @@ -1,7 +1,9 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.light.outputs import com.neovisionaries.i18n.CountryCode +import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel +import fr.gouv.cnsp.monitorfish.infrastructure.api.outputs.BeaconDataOutput import java.util.* data class VesselDataOutput( @@ -34,11 +36,11 @@ data class VesselDataOutput( val proprietorEmails: List? = null, val vesselPhones: List? = null, val vesselEmails: List? = null, - val beaconNumber: String? = null, + val beacon: BeaconDataOutput? = null, val underCharter: Boolean? = null, ) { companion object { - fun fromVessel(vessel: Vessel?): VesselDataOutput? { + fun fromVessel(vessel: Vessel?, beacon: Beacon?): VesselDataOutput? { if (vessel == null) { return null } @@ -73,7 +75,7 @@ data class VesselDataOutput( proprietorEmails = vessel.proprietorEmails, vesselPhones = vessel.vesselPhones, vesselEmails = vessel.vesselEmails, - beaconNumber = vessel.beaconNumber, + beacon = beacon?.let { BeaconDataOutput.fromBeacon(it) }, underCharter = vessel.underCharter, ) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/BeaconDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/BeaconDataOutput.kt new file mode 100644 index 0000000000..8d55957f03 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/BeaconDataOutput.kt @@ -0,0 +1,20 @@ +package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs + +import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon +import java.time.ZonedDateTime + +data class BeaconDataOutput( + val beaconNumber: String, + val isCoastal: Boolean? = null, + val loggingDatetimeUtc: ZonedDateTime? = null, +) { + companion object { + fun fromBeacon(beacon: Beacon): BeaconDataOutput { + return BeaconDataOutput( + beaconNumber = beacon.beaconNumber, + isCoastal = beacon.isCoastal, + loggingDatetimeUtc = beacon.loggingDatetimeUtc, + ) + } + } +} diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselAndPositionsDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselAndPositionsDataOutput.kt index d69a06b42f..a9cd06549d 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselAndPositionsDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselAndPositionsDataOutput.kt @@ -7,10 +7,11 @@ data class VesselAndPositionsDataOutput( val vessel: VesselDataOutput?, ) { companion object { - fun fromVesselWithData(vesselInformation: VesselInformation): VesselAndPositionsDataOutput { + fun fromVesselInformation(vesselInformation: VesselInformation): VesselAndPositionsDataOutput { return VesselAndPositionsDataOutput( - vessel = VesselDataOutput.fromVesselAndRiskFactor( + vessel = VesselDataOutput.fromVesselAndRelatedDatas( vesselInformation.vessel, + vesselInformation.beacon, vesselInformation.vesselRiskFactor, ), positions = vesselInformation.positions.map { diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt index e32c8bd3de..4c146135c3 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt @@ -1,6 +1,7 @@ package fr.gouv.cnsp.monitorfish.infrastructure.api.outputs import com.neovisionaries.i18n.CountryCode +import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon import fr.gouv.cnsp.monitorfish.domain.entities.risk_factor.VesselRiskFactor import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel import java.util.* @@ -35,12 +36,12 @@ data class VesselDataOutput( val proprietorEmails: List? = null, val vesselPhones: List? = null, val vesselEmails: List? = null, - val beaconNumber: String? = null, val riskFactor: RiskFactorDataOutput? = null, + val beacon: BeaconDataOutput? = null, val underCharter: Boolean? = null, ) { companion object { - fun fromVesselAndRiskFactor(vessel: Vessel?, vesselRiskFactor: VesselRiskFactor): VesselDataOutput? { + fun fromVesselAndRelatedDatas(vessel: Vessel?, beacon: Beacon?, vesselRiskFactor: VesselRiskFactor): VesselDataOutput? { if (vessel == null) { return null } @@ -75,7 +76,7 @@ data class VesselDataOutput( proprietorEmails = vessel.proprietorEmails, vesselPhones = vessel.vesselPhones, vesselEmails = vessel.vesselEmails, - beaconNumber = vessel.beaconNumber, + beacon = beacon?.let { BeaconDataOutput.fromBeacon(it) }, riskFactor = RiskFactorDataOutput.fromVesselRiskFactor(vesselRiskFactor), underCharter = vessel.underCharter, ) @@ -112,8 +113,6 @@ data class VesselDataOutput( proprietorEmails = vessel.proprietorEmails, vesselPhones = vessel.vesselPhones, vesselEmails = vessel.vesselEmails, - beaconNumber = vessel.beaconNumber, - riskFactor = null, underCharter = vessel.underCharter, ) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselIdentityDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselIdentityDataOutput.kt index d5db4907a8..4fe379d8c0 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselIdentityDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselIdentityDataOutput.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.vessel.Vessel +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselAndBeacon data class VesselIdentityDataOutput( val internalReferenceNumber: String? = null, @@ -16,18 +16,18 @@ data class VesselIdentityDataOutput( val beaconNumber: String? = null, ) { companion object { - fun fromVessel(vessel: Vessel): VesselIdentityDataOutput { + fun fromVesselAndBeacon(vesselAndBeacon: VesselAndBeacon): VesselIdentityDataOutput { return VesselIdentityDataOutput( - internalReferenceNumber = vessel.internalReferenceNumber, - districtCode = vessel.districtCode, - vesselId = vessel.id, - imo = vessel.imo, - ircs = vessel.ircs, - mmsi = vessel.mmsi, - externalReferenceNumber = vessel.externalReferenceNumber, - vesselName = vessel.vesselName, - flagState = vessel.flagState, - beaconNumber = vessel.beaconNumber, + internalReferenceNumber = vesselAndBeacon.vessel.internalReferenceNumber, + districtCode = vesselAndBeacon.vessel.districtCode, + vesselId = vesselAndBeacon.vessel.id, + imo = vesselAndBeacon.vessel.imo, + ircs = vesselAndBeacon.vessel.ircs, + mmsi = vesselAndBeacon.vessel.mmsi, + externalReferenceNumber = vesselAndBeacon.vessel.externalReferenceNumber, + vesselName = vesselAndBeacon.vessel.vesselName, + flagState = vesselAndBeacon.vessel.flagState, + beaconNumber = vesselAndBeacon.beacon?.beaconNumber, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/BeaconEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/BeaconEntity.kt index 4466b92c32..4754937f06 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/BeaconEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/BeaconEntity.kt @@ -3,6 +3,8 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.entities import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.BeaconStatus import jakarta.persistence.* +import java.time.Instant +import java.time.ZoneOffset @Entity @Table(name = "beacons") @@ -17,6 +19,11 @@ data class BeaconEntity( val beaconStatus: BeaconStatus? = null, @Column(name = "satellite_operator_id") val satelliteOperatorId: Int? = null, + @Column(name = "is_coastal") + val isCoastal: Boolean? = null, + @Column(name = "logging_datetime_utc") + val loggingDatetimeUtc: Instant? = null, + ) { fun toBeacon() = Beacon( @@ -24,5 +31,7 @@ data class BeaconEntity( vesselId = vesselId, beaconStatus = beaconStatus, satelliteOperatorId = satelliteOperatorId, + isCoastal = isCoastal, + loggingDatetimeUtc = loggingDatetimeUtc?.atZone(ZoneOffset.UTC), ) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepository.kt index e7a61a0406..a4f8c50b4e 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepository.kt @@ -20,9 +20,9 @@ class JpaBeaconRepository(private val dbBeaconRepository: DBBeaconRepository) : } @Cacheable(value = ["find_beacon"]) - override fun findBeaconNumberByVesselId(vesselId: Int): String? { + override fun findBeaconByVesselId(vesselId: Int): Beacon? { return try { - dbBeaconRepository.findByVesselId(vesselId).beaconNumber + dbBeaconRepository.findByVesselId(vesselId).toBeacon() } catch (e: EmptyResultDataAccessException) { return null } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt index e9c506a4a0..8d66bfcc93 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt @@ -3,6 +3,7 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases import com.neovisionaries.i18n.CountryCode import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.eq +import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon import fr.gouv.cnsp.monitorfish.domain.entities.position.Position import fr.gouv.cnsp.monitorfish.domain.entities.position.PositionType import fr.gouv.cnsp.monitorfish.domain.entities.risk_factor.VesselRiskFactor @@ -51,13 +52,13 @@ class GetVesselUTests { null, null, PositionType.AIS, - false, - false, - 16.445, - 48.2525, - 1.8, - 180.0, - now.minusHours( + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 4, ), ) @@ -71,13 +72,13 @@ class GetVesselUTests { null, null, PositionType.AIS, - false, - false, - 16.445, - 48.2525, - 1.8, - 180.0, - now.minusHours( + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 3, ), ) @@ -91,13 +92,13 @@ class GetVesselUTests { null, null, PositionType.AIS, - false, - false, - 16.445, - 48.2525, - 1.8, - 180.0, - now.minusHours( + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 2, ), ) @@ -111,13 +112,13 @@ class GetVesselUTests { null, null, PositionType.AIS, - false, - false, - 16.445, - 48.2525, - 1.8, - 180.0, - now.minusHours( + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 1, ), ) @@ -128,7 +129,7 @@ class GetVesselUTests { given(riskFactorRepository.findByInternalReferenceNumber(any())).willReturn( VesselRiskFactor(2.3, 2.0, 1.9, 3.2), ) - given(beaconRepository.findBeaconNumberByVesselId(eq(123))).willReturn("A_BEACON_NUMBER") + given(beaconRepository.findBeaconByVesselId(eq(123))).willReturn(Beacon("A_BEACON_NUMBER", vesselId = 123)) // When val pair = @@ -155,7 +156,7 @@ class GetVesselUTests { // Then assertThat(pair.first).isFalse assertThat(pair.second.vessel?.id).isEqualTo(123) - assertThat(pair.second.vessel?.beaconNumber).isEqualTo("A_BEACON_NUMBER") + assertThat(pair.second.beacon?.beaconNumber).isEqualTo("A_BEACON_NUMBER") assertThat(pair.second.positions.first().dateTime).isEqualTo(now.minusHours(4)) assertThat(pair.second.positions.last().dateTime).isEqualTo(now.minusHours(1)) assertThat(pair.second.vesselRiskFactor.impactRiskFactor).isEqualTo(2.3) @@ -206,7 +207,7 @@ class GetVesselUTests { ) given(vesselRepository.findVesselById(any())).willReturn(null) given(riskFactorRepository.findByInternalReferenceNumber(any())).willReturn(VesselRiskFactor()) - given(beaconRepository.findBeaconNumberByVesselId(eq(123))).willReturn(null) + given(beaconRepository.findBeaconByVesselId(eq(123))).willReturn(null) // When val pair = @@ -232,6 +233,6 @@ class GetVesselUTests { // Then assertThat(pair.first).isFalse - assertThat(pair.second.vessel?.beaconNumber).isNull() + assertThat(pair.second.beacon?.beaconNumber).isNull() } } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt index 729ca561b9..7466f1770f 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt @@ -60,8 +60,8 @@ class SearchVesselsUTests { // Then assertThat(vessels).hasSize(2) - assertThat(vessels.first().id).isEqualTo(1) - assertThat(vessels.last().id).isEqualTo(2) + assertThat(vessels.first().vessel.id).isEqualTo(1) + assertThat(vessels.last().vessel.id).isEqualTo(2) } @Test @@ -90,8 +90,10 @@ class SearchVesselsUTests { // Then assertThat(vessels).hasSize(2) - assertThat(vessels.first().internalReferenceNumber).isEqualTo("1234") - assertThat(vessels.last().internalReferenceNumber).isEqualTo("5789") + assertThat(vessels.first().vessel.internalReferenceNumber).isEqualTo("1234") + assertThat(vessels.first().beacon?.beaconNumber).isEqualTo("123") + assertThat(vessels.last().vessel.internalReferenceNumber).isEqualTo("5789") + assertThat(vessels.last().beacon?.beaconNumber).isEqualTo("12456") } @Test @@ -120,8 +122,8 @@ class SearchVesselsUTests { // Then assertThat(vessels).hasSize(3) - assertThat(vessels.first().internalReferenceNumber).isEqualTo("12345688415") - assertThat(vessels[1].internalReferenceNumber).isEqualTo("1234") - assertThat(vessels.last().internalReferenceNumber).isEqualTo("5789") + assertThat(vessels.first().vessel.internalReferenceNumber).isEqualTo("1234") + assertThat(vessels[1].vessel.internalReferenceNumber).isEqualTo("5789") + assertThat(vessels.last().vessel.internalReferenceNumber).isEqualTo("12345688415") } } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt index e2086eafbf..591652a19c 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt @@ -21,10 +21,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Reporting import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingValue import fr.gouv.cnsp.monitorfish.domain.entities.risk_factor.VesselRiskFactor -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselInformation -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselTrackDepth +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.* import fr.gouv.cnsp.monitorfish.domain.use_cases.TestUtils import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.VoyageRequest import fr.gouv.cnsp.monitorfish.domain.use_cases.vessel.* @@ -97,7 +94,22 @@ class VesselControllerITests { val farPastFixedDateTime = ZonedDateTime.of(EPOCH, LocalTime.MAX.plusSeconds(1), ZoneId.of("UTC")) val position = LastPosition( - 0, 1, "MMSI", null, null, null, null, CountryCode.FR, PositionType.AIS, 16.445, 48.2525, 16.445, 48.2525, 1.8, 180.0, farPastFixedDateTime, vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + id = 0, + vesselId = 1, + internalReferenceNumber = "MMSI", + mmsi = null, + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = CountryCode.FR, + positionType = PositionType.AIS, + latitude = 16.445, + longitude = 48.2525, + estimatedCurrentLatitude = 16.445, + estimatedCurrentLongitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = farPastFixedDateTime, vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, gearOnboard = listOf( gear, ), @@ -137,20 +149,59 @@ class VesselControllerITests { // Given val now = ZonedDateTime.now().minusDays(1) val firstPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 4, ), ) val secondPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 3, ), ) val thirdPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 2, ), ) @@ -160,7 +211,7 @@ class VesselControllerITests { Pair( false, VesselInformation( - Vessel( + vessel = Vessel( id = 123, internalReferenceNumber = "FR224226850", vesselName = "MY AWESOME VESSEL", @@ -169,8 +220,9 @@ class VesselControllerITests { vesselType = "Fishing", underCharter = true, ), - listOf(firstPosition, secondPosition, thirdPosition), - VesselRiskFactor(2.3, 2.0, 1.9, 3.2), + beacon = null, + positions = listOf(firstPosition, secondPosition, thirdPosition), + vesselRiskFactor = VesselRiskFactor(2.3, 2.0, 1.9, 3.2), ), ) } @@ -216,9 +268,10 @@ class VesselControllerITests { Pair( true, VesselInformation( - null, - listOf(), - VesselRiskFactor(2.3, 2.0, 1.9, 3.2), + vessel = null, + beacon = null, + positions = listOf(), + vesselRiskFactor = VesselRiskFactor(2.3, 2.0, 1.9, 3.2), ), ) } @@ -240,9 +293,10 @@ class VesselControllerITests { Pair( false, VesselInformation( - null, - listOf(), - VesselRiskFactor(2.3, 2.0, 1.9, 3.2), + vessel = null, + beacon = null, + positions = listOf(), + vesselRiskFactor = VesselRiskFactor(2.3, 2.0, 1.9, 3.2), ), ) } @@ -259,14 +313,14 @@ class VesselControllerITests { runBlocking { Mockito.verify(getVessel).execute( - null, - "FR224226850", - "123", - "IEF4", - VesselTrackDepth.CUSTOM, - VesselIdentifier.INTERNAL_REFERENCE_NUMBER, - ZonedDateTime.parse("2021-03-24T22:07:00Z"), - ZonedDateTime.parse("2021-04-24T22:07:00Z"), + vesselId = null, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "123", + ircs = "IEF4", + trackDepth = VesselTrackDepth.CUSTOM, + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + fromDateTime = ZonedDateTime.parse("2021-03-24T22:07:00Z"), + toDateTime = ZonedDateTime.parse("2021-04-24T22:07:00Z"), ) } } @@ -276,20 +330,59 @@ class VesselControllerITests { // Given val now = ZonedDateTime.now().minusDays(1) val firstPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 4, ), ) val secondPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 3, ), ) val thirdPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 2, ), ) @@ -327,21 +420,27 @@ class VesselControllerITests { // Given given(this.searchVessels.execute(any())).willReturn( listOf( - Vessel( - id = 1, - internalReferenceNumber = "FR224226850", - vesselName = "MY AWESOME VESSEL", - flagState = CountryCode.FR, - declaredFishingGears = listOf("Trémails"), - vesselType = "Fishing", + VesselAndBeacon( + vessel = Vessel( + id = 1, + internalReferenceNumber = "FR224226850", + vesselName = "MY AWESOME VESSEL", + flagState = CountryCode.FR, + declaredFishingGears = listOf("Trémails"), + vesselType = "Fishing", + ), + beacon = Beacon(beaconNumber = "123456", vesselId = 1), ), - Vessel( - id = 2, - internalReferenceNumber = "GBR21555445", - vesselName = "ANOTHER VESSEL", - flagState = CountryCode.GB, - declaredFishingGears = listOf("Trémails"), - vesselType = "Fishing", + VesselAndBeacon( + vessel = Vessel( + id = 2, + internalReferenceNumber = "GBR21555445", + vesselName = "ANOTHER VESSEL", + flagState = CountryCode.GB, + declaredFishingGears = listOf("Trémails"), + vesselType = "Fishing", + ), + beacon = null, ), ), ) @@ -354,6 +453,7 @@ class VesselControllerITests { .andExpect(jsonPath("$[0].flagState", equalTo("FR"))) .andExpect(jsonPath("$[0].vesselName", equalTo("MY AWESOME VESSEL"))) .andExpect(jsonPath("$[0].internalReferenceNumber", equalTo("FR224226850"))) + .andExpect(jsonPath("$[0].beaconNumber", equalTo("123456"))) .andExpect(jsonPath("$[1].flagState", equalTo("GB"))) .andExpect(jsonPath("$[1].vesselName", equalTo("ANOTHER VESSEL"))) .andExpect(jsonPath("$[1].internalReferenceNumber", equalTo("GBR21555445"))) @@ -449,9 +549,18 @@ class VesselControllerITests { history = listOf( BeaconMalfunctionWithDetails( beaconMalfunction = BeaconMalfunction( - 1, "FR224226850", "1236514", "IRCS", - "fr", VesselIdentifier.INTERNAL_REFERENCE_NUMBER, "BIDUBULE", VesselStatus.AT_SEA, Stage.END_OF_MALFUNCTION, - ZonedDateTime.now(), null, ZonedDateTime.now(), endOfBeaconMalfunctionReason = EndOfBeaconMalfunctionReason.RESUMED_TRANSMISSION, + id = 1, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "1236514", + ircs = "IRCS", + flagState = "fr", + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + vesselName = "BIDUBULE", + vesselStatus = VesselStatus.AT_SEA, + stage = Stage.END_OF_MALFUNCTION, + malfunctionStartDateTime = ZonedDateTime.now(), + malfunctionEndDateTime = null, + vesselStatusLastModificationDateTime = ZonedDateTime.now(), endOfBeaconMalfunctionReason = EndOfBeaconMalfunctionReason.RESUMED_TRANSMISSION, beaconNumber = "123465", beaconStatusAtMalfunctionCreation = BeaconStatus.ACTIVATED, vesselId = 123, ), comments = listOf( @@ -475,9 +584,18 @@ class VesselControllerITests { ), current = BeaconMalfunctionWithDetails( beaconMalfunction = BeaconMalfunction( - 2, "FR224226850", "1236514", "IRCS", - "fr", VesselIdentifier.INTERNAL_REFERENCE_NUMBER, "BIDUBULE", VesselStatus.AT_SEA, Stage.INITIAL_ENCOUNTER, - ZonedDateTime.now(), null, ZonedDateTime.now(), + id = 2, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "1236514", + ircs = "IRCS", + flagState = "fr", + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + vesselName = "BIDUBULE", + vesselStatus = VesselStatus.AT_SEA, + stage = Stage.INITIAL_ENCOUNTER, + malfunctionStartDateTime = ZonedDateTime.now(), + malfunctionEndDateTime = null, + vesselStatusLastModificationDateTime = ZonedDateTime.now(), beaconNumber = "123465", beaconStatusAtMalfunctionCreation = BeaconStatus.ACTIVATED, vesselId = 123, ), comments = listOf( diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt index 203dc10007..492d989b85 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt @@ -149,6 +149,7 @@ class VesselLightControllerITests { vesselType = "Fishing", underCharter = true, ), + null, listOf(firstPosition, secondPosition, thirdPosition), VesselRiskFactor(2.3, 2.0, 1.9, 3.2), ), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepositoryITests.kt index f6464fe5bf..0c312f0a80 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaBeaconRepositoryITests.kt @@ -45,7 +45,7 @@ class JpaBeaconRepositoryITests : AbstractDBTests() { @Transactional fun `findBeaconNumberByVesselId Should return null When no beacon is found`() { // When - val beaconNumber = jpaBeaconRepository.findBeaconNumberByVesselId(666) + val beaconNumber = jpaBeaconRepository.findBeaconByVesselId(666) assertThat(beaconNumber).isNull() } From 71842274f255196a049739530c4f0de8cde9c94f Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Fri, 31 May 2024 12:19:47 +0200 Subject: [PATCH 4/9] Add logbook information to vessel APIs --- .../domain/entities/vessel/Vessel.kt | 4 + .../GetPriorNotification.kt | 5 +- .../GetPriorNotifications.kt | 5 +- .../api/light/outputs/VesselDataOutput.kt | 4 + .../api/outputs/VesselDataOutput.kt | 6 + .../database/entities/LogbookReportEntity.kt | 7 +- .../database/entities/VesselEntity.kt | 7 + ...e.sql => V0.256__Update_vessels_table.sql} | 0 .../domain/entities/vessel/VesselUTests.kt | 3 + ...actionSuspicionWithDMLAndSeafrontUTests.kt | 20 +- .../use_cases/GetVesselPositionsUTests.kt | 302 +++++++++++++----- .../domain/use_cases/GetVesselUTests.kt | 5 +- .../domain/use_cases/SearchVesselsUTests.kt | 33 +- .../monitorfish/domain/use_cases/TestUtils.kt | 3 + .../GetActivityReportsUTests.kt | 12 + .../mission_actions/TestUtils.kt | 0 .../GetPriorNotificationUTests.kt | 2 + .../GetPriorNotificationsUTests.kt | 2 + .../api/bff/MissionActionsControllerITests.kt | 1 + .../bff/PriorNotificationControllerITests.kt | 3 + .../api/bff/VesselControllerITests.kt | 3 + .../api/light/VesselLightControllerITests.kt | 1 + 22 files changed, 322 insertions(+), 106 deletions(-) rename backend/src/main/resources/db/migration/internal/{V0.254__Update_vessels_table.sql => V0.256__Update_vessels_table.sql} (100%) rename backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/{ => mission}/mission_actions/TestUtils.kt (100%) 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 54f329eaa9..5b60daf5e0 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 @@ -39,6 +39,8 @@ data class Vessel( val vesselPhones: List? = null, val vesselEmails: List? = null, val underCharter: Boolean? = null, + val logbookEquipmentStatus: String? = null, + val hasLogbookEsacapt: Boolean, ) { fun isLessThanTwelveMetersVessel(): Boolean { return length?.let { it < 12.0 } ?: false @@ -106,3 +108,5 @@ val LIKELY_CONTROLLED_COUNTRY_CODES = CountryCode.SE.alpha3, CountryCode.GB.alpha3, ) + +val UNKNOWN_VESSEL = Vessel(id = -1, flagState = CountryCode.UNDEFINED, hasLogbookEsacapt = false) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotification.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotification.kt index a067350da8..14e9c254b0 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotification.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotification.kt @@ -1,6 +1,5 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification -import com.neovisionaries.i18n.CountryCode import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.facade.Seafront import fr.gouv.cnsp.monitorfish.domain.entities.logbook.LogbookMessageTyped @@ -8,7 +7,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.PNO import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotification import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType import fr.gouv.cnsp.monitorfish.domain.entities.reporting.filters.ReportingFilter -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.UNKNOWN_VESSEL import fr.gouv.cnsp.monitorfish.domain.exceptions.CodeNotFoundException import fr.gouv.cnsp.monitorfish.domain.exceptions.NoERSMessagesFound import fr.gouv.cnsp.monitorfish.domain.repositories.* @@ -62,7 +61,7 @@ class GetPriorNotification( val vessel = priorNotification.logbookMessageTyped.logbookMessage .internalReferenceNumber?.let { vesselInternalReferenceNumber -> allVessels.find { it.internalReferenceNumber == vesselInternalReferenceNumber } - } ?: Vessel(id = -1, flagState = CountryCode.UNDEFINED) + } ?: UNKNOWN_VESSEL val vesselRiskFactor = vessel.internalReferenceNumber?.let { vesselInternalReferenceNumber -> allRiskFactors.find { it.internalReferenceNumber == vesselInternalReferenceNumber } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotifications.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotifications.kt index 6761c63aa9..7596f969cd 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotifications.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotifications.kt @@ -1,6 +1,5 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification -import com.neovisionaries.i18n.CountryCode import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.facade.Seafront import fr.gouv.cnsp.monitorfish.domain.entities.logbook.filters.LogbookReportFilter @@ -8,7 +7,7 @@ import fr.gouv.cnsp.monitorfish.domain.entities.logbook.sorters.LogbookReportSor import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotification import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType import fr.gouv.cnsp.monitorfish.domain.entities.reporting.filters.ReportingFilter -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.UNKNOWN_VESSEL import fr.gouv.cnsp.monitorfish.domain.exceptions.CodeNotFoundException import fr.gouv.cnsp.monitorfish.domain.repositories.* import org.springframework.data.domain.Sort @@ -49,7 +48,7 @@ class GetPriorNotifications( val vessel = priorNotification.logbookMessageTyped.logbookMessage .internalReferenceNumber?.let { vesselInternalReferenceNumber -> allVessels.find { it.internalReferenceNumber == vesselInternalReferenceNumber } - } ?: Vessel(id = -1, flagState = CountryCode.UNDEFINED) + } ?: UNKNOWN_VESSEL val vesselRiskFactor = vessel.internalReferenceNumber?.let { vesselInternalReferenceNumber -> allRiskFactors.find { it.internalReferenceNumber == vesselInternalReferenceNumber } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt index 7c4274faae..893a6136c3 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt @@ -38,6 +38,8 @@ data class VesselDataOutput( val vesselEmails: List? = null, val beacon: BeaconDataOutput? = null, val underCharter: Boolean? = null, + val logbookEquipmentStatus: String? = null, + val hasLogbookEsacapt: Boolean, ) { companion object { fun fromVessel(vessel: Vessel?, beacon: Beacon?): VesselDataOutput? { @@ -77,6 +79,8 @@ data class VesselDataOutput( vesselEmails = vessel.vesselEmails, beacon = beacon?.let { BeaconDataOutput.fromBeacon(it) }, underCharter = vessel.underCharter, + logbookEquipmentStatus = vessel.logbookEquipmentStatus, + hasLogbookEsacapt = vessel.hasLogbookEsacapt, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt index 4c146135c3..16aa473793 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/VesselDataOutput.kt @@ -39,6 +39,8 @@ data class VesselDataOutput( val riskFactor: RiskFactorDataOutput? = null, val beacon: BeaconDataOutput? = null, val underCharter: Boolean? = null, + val logbookEquipmentStatus: String? = null, + val hasLogbookEsacapt: Boolean, ) { companion object { fun fromVesselAndRelatedDatas(vessel: Vessel?, beacon: Beacon?, vesselRiskFactor: VesselRiskFactor): VesselDataOutput? { @@ -79,6 +81,8 @@ data class VesselDataOutput( beacon = beacon?.let { BeaconDataOutput.fromBeacon(it) }, riskFactor = RiskFactorDataOutput.fromVesselRiskFactor(vesselRiskFactor), underCharter = vessel.underCharter, + logbookEquipmentStatus = vessel.logbookEquipmentStatus, + hasLogbookEsacapt = vessel.hasLogbookEsacapt, ) } @@ -114,6 +118,8 @@ data class VesselDataOutput( vesselPhones = vessel.vesselPhones, vesselEmails = vessel.vesselEmails, underCharter = vessel.underCharter, + logbookEquipmentStatus = vessel.logbookEquipmentStatus, + hasLogbookEsacapt = vessel.hasLogbookEsacapt, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/LogbookReportEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/LogbookReportEntity.kt index 93f3d9196e..449513b064 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/LogbookReportEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/LogbookReportEntity.kt @@ -1,11 +1,10 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.entities import com.fasterxml.jackson.databind.ObjectMapper -import com.neovisionaries.i18n.CountryCode import fr.gouv.cnsp.monitorfish.domain.entities.logbook.* import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.PNO import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotification -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.UNKNOWN_VESSEL import fr.gouv.cnsp.monitorfish.domain.mappers.ERSMapper.getERSMessageValueFromJSON import io.hypersistence.utils.hibernate.type.json.JsonBinaryType import jakarta.persistence.* @@ -148,8 +147,8 @@ data class LogbookReportEntity( val relatedLogbookMessages = relatedModels.map { it.toLogbookMessage(mapper) } val enrichedLogbookMessageTyped = referenceLogbookMessage .toEnrichedLogbookMessageTyped(relatedLogbookMessages, PNO::class.java) - // For pratical reasons `vessel` can't be `null`, so we temporarely set it to "Navire inconnu" - val vessel = Vessel(id = -1, flagState = CountryCode.UNDEFINED) + // For practical reasons `vessel` can't be `null`, so we temporarily set it to "Navire inconnu" + val vessel = UNKNOWN_VESSEL return PriorNotification( fingerprint, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/VesselEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/VesselEntity.kt index e91ffa2028..ded256c19e 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/VesselEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/VesselEntity.kt @@ -80,6 +80,11 @@ data class VesselEntity( val vesselEmails: List? = null, @Column(name = "under_charter") val underCharter: Boolean? = null, + /** Logbook */ + @Column(name = "logbook_equipment_status") + val logbookEquipmentStatus: String? = null, + @Column(name = "has_esacapt") + val hasLogbookEsacapt: Boolean, ) { fun toVessel() = Vessel( @@ -111,6 +116,8 @@ data class VesselEntity( vesselPhones = vesselPhones, vesselEmails = vesselEmails, underCharter = underCharter, + logbookEquipmentStatus = logbookEquipmentStatus, + hasLogbookEsacapt = hasLogbookEsacapt, ) companion object { diff --git a/backend/src/main/resources/db/migration/internal/V0.254__Update_vessels_table.sql b/backend/src/main/resources/db/migration/internal/V0.256__Update_vessels_table.sql similarity index 100% rename from backend/src/main/resources/db/migration/internal/V0.254__Update_vessels_table.sql rename to backend/src/main/resources/db/migration/internal/V0.256__Update_vessels_table.sql diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselUTests.kt index 4542dd2163..5edc9c16f6 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/vessel/VesselUTests.kt @@ -20,6 +20,7 @@ class VesselUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ) // When @@ -40,6 +41,7 @@ class VesselUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "", + hasLogbookEsacapt = false, ) // When @@ -60,6 +62,7 @@ class VesselUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ) // When diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetInfractionSuspicionWithDMLAndSeafrontUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetInfractionSuspicionWithDMLAndSeafrontUTests.kt index fb26949f6f..5e9b4797a2 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetInfractionSuspicionWithDMLAndSeafrontUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetInfractionSuspicionWithDMLAndSeafrontUTests.kt @@ -39,7 +39,7 @@ class GetInfractionSuspicionWithDMLAndSeafrontUTests { ) given(vesselRepository.findVesselById(eq(123))).willReturn( - Vessel(id = 1, districtCode = "LO", flagState = CountryCode.FR), + Vessel(id = 1, districtCode = "LO", flagState = CountryCode.FR, hasLogbookEsacapt = false), ) given(districtRepository.find(eq("LO"))) .willReturn(District("LO", "Lorient", "56", "Morbihan", "DML 56", "NAMO")) @@ -50,8 +50,8 @@ class GetInfractionSuspicionWithDMLAndSeafrontUTests { vesselRepository, districtRepository, ).execute( - expectedInfractionSuspicion, - 123, + infractionSuspicion = expectedInfractionSuspicion, + vesselId = 123, ) // Then @@ -65,14 +65,14 @@ class GetInfractionSuspicionWithDMLAndSeafrontUTests { val throwable = catchThrowable { GetInfractionSuspicionWithDMLAndSeaFront(vesselRepository, districtRepository).execute( - InfractionSuspicion( + infractionSuspicion = InfractionSuspicion( reportingActor = ReportingActor.OPS, dml = "", natinfCode = 1235, authorTrigram = "LTH", title = "Chalut en boeuf illégal", ), - null, + vesselId = null, ) } @@ -89,14 +89,14 @@ class GetInfractionSuspicionWithDMLAndSeafrontUTests { val throwable = catchThrowable { GetInfractionSuspicionWithDMLAndSeaFront(vesselRepository, districtRepository).execute( - InfractionSuspicion( + infractionSuspicion = InfractionSuspicion( reportingActor = ReportingActor.OPS, dml = "", natinfCode = 1235, authorTrigram = "LTH", title = "Chalut en boeuf illégal", ), - 123, + vesselId = 123, ) } @@ -108,7 +108,7 @@ class GetInfractionSuspicionWithDMLAndSeafrontUTests { fun `execute Should not throw an exception When the district is not found`() { // Given given(vesselRepository.findVesselById(eq(123))).willReturn( - Vessel(id = 1, flagState = CountryCode.FR, districtCode = "LO"), + Vessel(id = 1, flagState = CountryCode.FR, districtCode = "LO", hasLogbookEsacapt = false), ) given(districtRepository.find(eq("LO"))) .willThrow(CodeNotFoundException("oupsi")) @@ -117,14 +117,14 @@ class GetInfractionSuspicionWithDMLAndSeafrontUTests { val throwable = catchThrowable { GetInfractionSuspicionWithDMLAndSeaFront(vesselRepository, districtRepository).execute( - InfractionSuspicion( + infractionSuspicion = InfractionSuspicion( reportingActor = ReportingActor.OPS, dml = "", natinfCode = 1235, authorTrigram = "LTH", title = "Chalut en boeuf illégal", ), - 123, + vesselId = 123, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselPositionsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselPositionsUTests.kt index 3b95e6483d..0c144b5ab3 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselPositionsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselPositionsUTests.kt @@ -35,26 +35,78 @@ class GetVesselPositionsUTests { // Given val now = ZonedDateTime.now().minusDays(1) val firstPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 4, ), ) val secondPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 3, ), ) val thirdPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 2, ), ) val fourthPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 1, ), ) @@ -69,13 +121,13 @@ class GetVesselPositionsUTests { val pair = runBlocking { GetVesselPositions(positionRepository, logbookReportRepository) .execute( - "FR224226850", - "", - "", - VesselTrackDepth.LAST_DEPARTURE, - VesselIdentifier.INTERNAL_REFERENCE_NUMBER, - null, - null, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "", + ircs = "", + trackDepth = VesselTrackDepth.LAST_DEPARTURE, + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + fromDateTime = null, + toDateTime = null, ) } @@ -99,13 +151,13 @@ class GetVesselPositionsUTests { runBlocking { GetVesselPositions(positionRepository, logbookReportRepository) .execute( - "FR224226850", - "", - "", - VesselTrackDepth.TWELVE_HOURS, - VesselIdentifier.INTERNAL_REFERENCE_NUMBER, - null, - null, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "", + ircs = "", + trackDepth = VesselTrackDepth.TWELVE_HOURS, + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + fromDateTime = null, + toDateTime = null, ) } } @@ -126,13 +178,13 @@ class GetVesselPositionsUTests { runBlocking { GetVesselPositions(positionRepository, logbookReportRepository) .execute( - "FR224226850", - "", - "", - VesselTrackDepth.LAST_DEPARTURE, - VesselIdentifier.INTERNAL_REFERENCE_NUMBER, - null, - null, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "", + ircs = "", + trackDepth = VesselTrackDepth.LAST_DEPARTURE, + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + fromDateTime = null, + toDateTime = null, ) } } @@ -153,13 +205,13 @@ class GetVesselPositionsUTests { runBlocking { GetVesselPositions(positionRepository, logbookReportRepository) .execute( - "FR224226850", - "", - "", - VesselTrackDepth.CUSTOM, - VesselIdentifier.INTERNAL_REFERENCE_NUMBER, - null, - ZonedDateTime.now(), + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "", + ircs = "", + trackDepth = VesselTrackDepth.CUSTOM, + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + fromDateTime = null, + toDateTime = ZonedDateTime.now(), ) } } @@ -182,13 +234,13 @@ class GetVesselPositionsUTests { runBlocking { GetVesselPositions(positionRepository, logbookReportRepository) .execute( - "FR224226850", - "", - "", - VesselTrackDepth.CUSTOM, - VesselIdentifier.INTERNAL_REFERENCE_NUMBER, - fromDateTime, - toDateTime, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "", + ircs = "", + trackDepth = VesselTrackDepth.CUSTOM, + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + fromDateTime = fromDateTime, + toDateTime = toDateTime, ) } @@ -205,26 +257,78 @@ class GetVesselPositionsUTests { // Given val now = ZonedDateTime.now().minusDays(1) val firstPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 4, ), ) val secondPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 3, ), ) val thirdPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 2, ), ) val fourthPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 1, ), ) @@ -236,13 +340,13 @@ class GetVesselPositionsUTests { runBlocking { GetVesselPositions(positionRepository, logbookReportRepository) .execute( - "FR224226850", - "", - "", - VesselTrackDepth.TWELVE_HOURS, - VesselIdentifier.INTERNAL_REFERENCE_NUMBER, - null, - null, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "", + ircs = "", + trackDepth = VesselTrackDepth.TWELVE_HOURS, + vesselIdentifier = VesselIdentifier.INTERNAL_REFERENCE_NUMBER, + fromDateTime = null, + toDateTime = null, ) } @@ -259,26 +363,78 @@ class GetVesselPositionsUTests { // Given val now = ZonedDateTime.now().minusDays(1) val firstPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 4, ), ) val secondPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 3, ), ) val thirdPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 2, ), ) val fourthPosition = Position( - null, "FR224226850", "224226850", null, null, null, null, PositionType.AIS, false, false, 16.445, 48.2525, 1.8, 180.0, - now.minusHours( + id = null, + internalReferenceNumber = "FR224226850", + mmsi = "224226850", + ircs = null, + externalReferenceNumber = null, + vesselName = null, + flagState = null, + positionType = PositionType.AIS, + isManual = false, + isFishing = false, + latitude = 16.445, + longitude = 48.2525, + speed = 1.8, + course = 180.0, + dateTime = now.minusHours( 1, ), ) @@ -292,13 +448,13 @@ class GetVesselPositionsUTests { val pair = runBlocking { GetVesselPositions(positionRepository, logbookReportRepository) .execute( - "FR224226850", - "", - "", - VesselTrackDepth.TWELVE_HOURS, - null, - null, - null, + internalReferenceNumber = "FR224226850", + externalReferenceNumber = "", + ircs = "", + trackDepth = VesselTrackDepth.TWELVE_HOURS, + vesselIdentifier = null, + fromDateTime = null, + toDateTime = null, ) } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt index 8d66bfcc93..001a5c60f0 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt @@ -1,16 +1,15 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases -import com.neovisionaries.i18n.CountryCode import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.eq import fr.gouv.cnsp.monitorfish.domain.entities.beacon_malfunctions.Beacon import fr.gouv.cnsp.monitorfish.domain.entities.position.Position import fr.gouv.cnsp.monitorfish.domain.entities.position.PositionType import fr.gouv.cnsp.monitorfish.domain.entities.risk_factor.VesselRiskFactor -import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselTrackDepth import fr.gouv.cnsp.monitorfish.domain.repositories.* +import fr.gouv.cnsp.monitorfish.domain.use_cases.TestUtils.DUMMY_VESSEL import fr.gouv.cnsp.monitorfish.domain.use_cases.vessel.GetVessel import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat @@ -125,7 +124,7 @@ class GetVesselUTests { given(positionRepository.findVesselLastPositionsByInternalReferenceNumber(any(), any(), any())).willReturn( listOf(firstPosition, fourthPosition, secondPosition, thirdPosition), ) - given(vesselRepository.findVesselById(any())).willReturn(Vessel(id = 123, flagState = CountryCode.FR)) + given(vesselRepository.findVesselById(any())).willReturn(DUMMY_VESSEL) given(riskFactorRepository.findByInternalReferenceNumber(any())).willReturn( VesselRiskFactor(2.3, 2.0, 1.9, 3.2), ) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt index 7466f1770f..caae2e0503 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/SearchVesselsUTests.kt @@ -27,7 +27,9 @@ class SearchVesselsUTests { @Test fun `execute Should return no vessel When there is no identification number`() { // Given - given(vesselRepository.search(any())).willReturn(listOf(Vessel(id = 1, flagState = CountryCode.FR))) + given(vesselRepository.search(any())).willReturn( + listOf(Vessel(id = 1, flagState = CountryCode.FR, hasLogbookEsacapt = false)), + ) // When val vessels = SearchVessels(vesselRepository, beaconRepository).execute("DUMMY VESSEL") @@ -39,11 +41,13 @@ class SearchVesselsUTests { @Test fun `execute Should return vessels When there is a match with a beacon`() { // Given - given(vesselRepository.search(any())).willReturn(listOf(Vessel(id = 1, flagState = CountryCode.FR))) + given(vesselRepository.search(any())).willReturn( + listOf(Vessel(id = 1, flagState = CountryCode.FR, hasLogbookEsacapt = false)), + ) given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn( listOf( - Vessel(1, internalReferenceNumber = "1234", flagState = CountryCode.FR), - Vessel(2, internalReferenceNumber = "5789", flagState = CountryCode.FR), + Vessel(1, internalReferenceNumber = "1234", flagState = CountryCode.FR, hasLogbookEsacapt = false), + Vessel(2, internalReferenceNumber = "5789", flagState = CountryCode.FR, hasLogbookEsacapt = false), ), ) given(beaconRepository.search(any())) @@ -68,7 +72,9 @@ class SearchVesselsUTests { fun `execute Should return vessels When there is a match with a beacon and the same vessel found in the vessel table`() { // Given given(vesselRepository.search(any())).willReturn( - listOf(Vessel(id = 1, internalReferenceNumber = "1234", flagState = CountryCode.FR)), + listOf( + Vessel(id = 1, internalReferenceNumber = "1234", flagState = CountryCode.FR, hasLogbookEsacapt = false), + ), ) given(beaconRepository.search(any())) .willReturn( @@ -80,8 +86,8 @@ class SearchVesselsUTests { ) given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn( listOf( - Vessel(1, internalReferenceNumber = "1234", flagState = CountryCode.FR), - Vessel(2, internalReferenceNumber = "5789", flagState = CountryCode.FR), + Vessel(1, internalReferenceNumber = "1234", flagState = CountryCode.FR, hasLogbookEsacapt = false), + Vessel(2, internalReferenceNumber = "5789", flagState = CountryCode.FR, hasLogbookEsacapt = false), ), ) @@ -100,7 +106,14 @@ class SearchVesselsUTests { fun `execute Should return vessels When there is a match with a beacon, the same vessel found in the vessel table and another vessel concatenated`() { // Given given(vesselRepository.search(any())).willReturn( - listOf(Vessel(id = 123456, internalReferenceNumber = "12345688415", flagState = CountryCode.FR)), + listOf( + Vessel( + id = 123456, + internalReferenceNumber = "12345688415", + flagState = CountryCode.FR, + hasLogbookEsacapt = false, + ), + ), ) given(beaconRepository.search(any())) .willReturn( @@ -112,8 +125,8 @@ class SearchVesselsUTests { ) given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn( listOf( - Vessel(1, internalReferenceNumber = "1234", flagState = CountryCode.FR), - Vessel(2, internalReferenceNumber = "5789", flagState = CountryCode.FR), + Vessel(1, internalReferenceNumber = "1234", flagState = CountryCode.FR, hasLogbookEsacapt = false), + Vessel(2, internalReferenceNumber = "5789", flagState = CountryCode.FR, hasLogbookEsacapt = false), ), ) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt index 4d12702d41..3a74e1a262 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt @@ -7,11 +7,14 @@ import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.* import fr.gouv.cnsp.monitorfish.domain.entities.reporting.Reporting import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingType import fr.gouv.cnsp.monitorfish.domain.entities.reporting.ReportingValue +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import java.time.ZoneOffset.UTC import java.time.ZonedDateTime object TestUtils { + val DUMMY_VESSEL = Vessel(id = 123, flagState = CountryCode.FR, hasLogbookEsacapt = false) + fun getDummyReportings(): List { return listOf( Reporting( 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 b0aeee57a8..f904a0c20d 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 @@ -122,6 +122,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), Vessel( id = 2, @@ -131,6 +132,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "LO", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn(vessels) @@ -261,6 +263,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), Vessel( id = 2, @@ -270,6 +273,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "LO", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn(vessels) @@ -382,6 +386,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1)))).willReturn(vessels) @@ -463,6 +468,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1)))).willReturn(vessels) @@ -537,6 +543,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1)))).willReturn(vessels) @@ -643,6 +650,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), Vessel( id = 2, @@ -652,6 +660,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "LO", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn(vessels) @@ -756,6 +765,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), Vessel( id = 2, @@ -765,6 +775,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "LO", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn(vessels) @@ -835,6 +846,7 @@ class GetActivityReportsUTests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), ) given(vesselRepository.findVesselsByIds(eq(listOf(1)))).willReturn(vessels) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission_actions/TestUtils.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/TestUtils.kt similarity index 100% rename from backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission_actions/TestUtils.kt rename to backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/TestUtils.kt diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationUTests.kt index 5f3a2158a1..1b7b322494 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationUTests.kt @@ -80,6 +80,7 @@ class GetPriorNotificationUTests { mmsi = null, underCharter = null, vesselName = null, + hasLogbookEsacapt = false, ), vesselRiskFactor = null, ), @@ -139,6 +140,7 @@ class GetPriorNotificationUTests { mmsi = null, underCharter = null, vesselName = null, + hasLogbookEsacapt = false, ), vesselRiskFactor = null, ), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationsUTests.kt index c8f12b534a..2d0be88054 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/GetPriorNotificationsUTests.kt @@ -89,6 +89,7 @@ class GetPriorNotificationsUTests { mmsi = null, underCharter = null, vesselName = null, + hasLogbookEsacapt = false, ), vesselRiskFactor = null, ), @@ -125,6 +126,7 @@ class GetPriorNotificationsUTests { mmsi = null, underCharter = null, vesselName = null, + hasLogbookEsacapt = false, ), vesselRiskFactor = null, ), 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 cfd0067365..11ee41e1ef 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 @@ -371,6 +371,7 @@ class MissionActionsControllerITests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", districtCode = "AY", + hasLogbookEsacapt = false, ), ), ), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationControllerITests.kt index dc1ef71ec1..fef177cfce 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/PriorNotificationControllerITests.kt @@ -80,6 +80,7 @@ class PriorNotificationControllerITests { mmsi = null, underCharter = null, vesselName = null, + hasLogbookEsacapt = false, ), vesselRiskFactor = null, ), @@ -116,6 +117,7 @@ class PriorNotificationControllerITests { mmsi = null, underCharter = null, vesselName = null, + hasLogbookEsacapt = false, ), vesselRiskFactor = null, ), @@ -189,6 +191,7 @@ class PriorNotificationControllerITests { mmsi = null, underCharter = null, vesselName = null, + hasLogbookEsacapt = false, ), vesselRiskFactor = null, ), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt index 591652a19c..1471119a8c 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/VesselControllerITests.kt @@ -219,6 +219,7 @@ class VesselControllerITests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", underCharter = true, + hasLogbookEsacapt = false, ), beacon = null, positions = listOf(firstPosition, secondPosition, thirdPosition), @@ -428,6 +429,7 @@ class VesselControllerITests { flagState = CountryCode.FR, declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", + hasLogbookEsacapt = false, ), beacon = Beacon(beaconNumber = "123456", vesselId = 1), ), @@ -439,6 +441,7 @@ class VesselControllerITests { flagState = CountryCode.GB, declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", + hasLogbookEsacapt = false, ), beacon = null, ), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt index 492d989b85..8237323982 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/VesselLightControllerITests.kt @@ -148,6 +148,7 @@ class VesselLightControllerITests { declaredFishingGears = listOf("Trémails"), vesselType = "Fishing", underCharter = true, + hasLogbookEsacapt = false, ), null, listOf(firstPosition, secondPosition, thirdPosition), From 2f3546a9d74fcb4997a064754f932243f6e58cf2 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Mon, 3 Jun 2024 11:28:49 +0200 Subject: [PATCH 5/9] Add hasVisioCaptures to Vessel entity --- .../entities/logbook/LogbookSoftware.kt | 11 ++++++++ .../domain/entities/vessel/Vessel.kt | 1 + .../repositories/LogbookReportRepository.kt | 2 ++ .../domain/use_cases/vessel/GetVessel.kt | 13 ++++++++- .../api/light/outputs/VesselDataOutput.kt | 2 ++ .../JpaLogbookReportRepository.kt | 4 +++ .../interfaces/DBLogbookReportRepository.kt | 16 +++++++++++ .../V666.2.0__Insert_dummy_vessels.sql | 28 +++++++++---------- .../V666.23__Insert_dummy_beacons.sql | 20 ++++++------- .../entities/logbook/LogbookSoftwareUTests.kt | 24 ++++++++++++++++ .../domain/use_cases/GetVesselUTests.kt | 2 ++ .../monitorfish/domain/use_cases/TestUtils.kt | 7 ++++- .../JpaLogbookReportRepositoryITests.kt | 20 +++++++++++++ 13 files changed, 124 insertions(+), 26 deletions(-) create mode 100644 backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt index 34b06042e9..8cceeecd2c 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt @@ -3,4 +3,15 @@ package fr.gouv.cnsp.monitorfish.domain.entities.logbook enum class LogbookSoftware(val software: String) { VISIOCAPTURE("VISIOCaptures"), E_SACAPT("e-Sacapt"), + ; + + companion object { + fun isVisioCapture(software: String?): Boolean { + val isVisioCapture = software?.let { + it.startsWith("FT") || it.startsWith("JT") + } + + return isVisioCapture ?: false + } + } } 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 5b60daf5e0..a225d9a850 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 @@ -41,6 +41,7 @@ data class Vessel( val underCharter: Boolean? = null, val logbookEquipmentStatus: String? = null, val hasLogbookEsacapt: Boolean, + val hasVisioCaptures: Boolean? = null, ) { fun isLessThanTwelveMetersVessel(): Boolean { return length?.let { it < 12.0 } ?: false diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/LogbookReportRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/LogbookReportRepository.kt index 362449aa84..c3b9210830 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/LogbookReportRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/LogbookReportRepository.kt @@ -64,6 +64,8 @@ interface LogbookReportRepository { fun findDistinctPriorNotificationTypes(): List + fun findLastReportSoftware(internalReferenceNumber: String): String? + // For test purpose fun deleteAll() diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt index baf6cc2768..7ecc15d204 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt @@ -1,7 +1,9 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.vessel import fr.gouv.cnsp.monitorfish.config.UseCase +import fr.gouv.cnsp.monitorfish.domain.entities.logbook.LogbookSoftware import fr.gouv.cnsp.monitorfish.domain.entities.risk_factor.VesselRiskFactor +import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselIdentifier import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselInformation import fr.gouv.cnsp.monitorfish.domain.entities.vessel.VesselTrackDepth @@ -59,11 +61,12 @@ class GetVessel( val vessel = vesselFuture.await() val beacon = vessel?.id?.let { vesselId -> beaconRepository.findBeaconByVesselId(vesselId) } + val hasVisioCaptures = hasVisioCaptures(vessel) Pair( vesselTrackHasBeenModified, VesselInformation( - vessel, + vessel?.copy(hasVisioCaptures = hasVisioCaptures), beacon, positions.await(), vesselRiskFactorsFuture.await() ?: VesselRiskFactor(), @@ -71,4 +74,12 @@ class GetVessel( ) } } + + private fun hasVisioCaptures(vessel: Vessel?) = vessel?.internalReferenceNumber?.let { + val software = logbookReportRepository.findLastReportSoftware( + it, + ) + + return@let LogbookSoftware.isVisioCapture(software) + } ?: false } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt index 893a6136c3..0a016ced95 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/light/outputs/VesselDataOutput.kt @@ -40,6 +40,7 @@ data class VesselDataOutput( val underCharter: Boolean? = null, val logbookEquipmentStatus: String? = null, val hasLogbookEsacapt: Boolean, + val hasVisioCaptures: Boolean? = null, ) { companion object { fun fromVessel(vessel: Vessel?, beacon: Beacon?): VesselDataOutput? { @@ -81,6 +82,7 @@ data class VesselDataOutput( underCharter = vessel.underCharter, logbookEquipmentStatus = vessel.logbookEquipmentStatus, hasLogbookEsacapt = vessel.hasLogbookEsacapt, + hasVisioCaptures = vessel.hasVisioCaptures, ) } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt index 27c214e0df..854ab16cd7 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepository.kt @@ -324,6 +324,10 @@ class JpaLogbookReportRepository( return dbERSRepository.findLastTwoYearsTripNumbers(internalReferenceNumber) } + override fun findLastReportSoftware(internalReferenceNumber: String): String? { + return dbERSRepository.findLastReportSoftware(internalReferenceNumber) + } + // For test purpose @Modifying @Transactional diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt index 5e8bfbcdcb..e6a93b5b7d 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/interfaces/DBLogbookReportRepository.kt @@ -351,6 +351,22 @@ interface DBLogbookReportRepository : ) fun findLastOperationDateTime(): Instant + @Query( + """ + SELECT + software + FROM + logbook_reports + where + cfr = :internalReferenceNumber AND + operation_datetime_utc < now() + ORDER BY operation_datetime_utc DESC + LIMIT 1 + """, + nativeQuery = true, + ) + fun findLastReportSoftware(internalReferenceNumber: String): String? + @Query( """select * from logbook_reports diff --git a/backend/src/main/resources/db/testdata/V666.2.0__Insert_dummy_vessels.sql b/backend/src/main/resources/db/testdata/V666.2.0__Insert_dummy_vessels.sql index 1322010e9f..885ddcff7b 100644 --- a/backend/src/main/resources/db/testdata/V666.2.0__Insert_dummy_vessels.sql +++ b/backend/src/main/resources/db/testdata/V666.2.0__Insert_dummy_vessels.sql @@ -4,58 +4,58 @@ INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, declared_fishing_gears, nav_licence_expiration_date, operator_name, operator_phones, operator_email, proprietor_name, proprietor_phones, proprietor_emails, - vessel_emails, vessel_phones, under_charter) + vessel_emails, vessel_phones, under_charter, has_esacapt, logbook_equipment_status) VALUES (1, 'FAK000999999', '224103750', 'CALLME', 'DONTSINK', 'PHENOMENE', 'FR', 5.0, 12.89, 'Auray', 'AY', 123, 'LORIENT', 127, 'Pêche côtière', '3', 'Pêche', ARRAY ['SDN', 'OTM'], CURRENT_DATE, 'LE PELETIER', ARRAY ['+33 6 84 56 32 14'], - 'lepeletier@gmail.com', 'EL MALIK', ARRAY ['+33 6 45 25 14'], ARRAY ['elmalik@gmail.com'], NULL, NULL, false), + 'lepeletier@gmail.com', 'EL MALIK', ARRAY ['+33 6 45 25 14'], ARRAY ['elmalik@gmail.com'], NULL, NULL, false, false, 'Equipé'), (2, 'U_W0NTFINDME', 'ABC123456', 'QGDF', 'TALK2ME', 'MALOTRU', 'X', 5.0, 12.89, 'Auray', 'AY', 123, 'LORIENT', 127, 'Pêche côtière', '3', 'Pêche', ARRAY ['SDN', 'OTM'], CURRENT_DATE, 'LE PELETIER', ARRAY ['+33 6 84 56 32 14'], 'lepeletier@gmail.com', 'EL MALIK', ARRAY ['+33 6 45 25 14'], - ARRAY ['elmalik@gmail.com'], ARRAY ['whatisyouremail@idontknow.anything'], NULL, true), + ARRAY ['elmalik@gmail.com'], ARRAY ['whatisyouremail@idontknow.anything'], NULL, true, false, 'Equipé'), (3, 'FR263418260', '224103750', 'IR12A', '08FR65324', 'LE b@TO', 'FR', 6.0, 14, 'Lorient', 'LT', 123, 'BREST', 237, 'Pêche côtière', '3', 'Pêche', ARRAY ['SDN', 'OTB', 'LNB'], CURRENT_DATE, 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (4, '', null, null, '', 'NO NAME', 'FR', 6.0, 14, 'Lorient', 'LT', 123, 'BREST', 237, 'Pêche côtière', '3', 'Pêche', ARRAY ['SDN', 'OTB', 'LNB'], CURRENT_DATE, 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], false), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], false, false, 'Equipé'), (5, 'SOCR4T3', null, 'SCRT', 'LePhiloFilou', 'SOCRATE', 'FR', 4.0, 19.29, 'Athènes', 'AT', 293, 'ATHENES', 531, 'Pêche et abricots', '4', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE, 'PARTHENON PHISHING', ARRAY ['+33 6 84 56 32 19'], 'parthenon@phishing.gr', 'ZEUS', ARRAY ['+33 6 45 25 19'], - ARRAY ['zeus@phishing.gr'], ARRAY ['brossetoilesdents@email.diamant'], NULL, true), + ARRAY ['zeus@phishing.gr'], ARRAY ['brossetoilesdents@email.diamant'], NULL, true, false, 'Equipé'), (6, 'ABC000939217', '', 'SC6082', 'RU460262', 'FRAIS AVIS MODE', 'FR', 15.5, 7.6, 'Auray', 'AY', 130, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE, 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (7, 'ABC000717263', '', 'QP6933', 'IG860866', 'ÉCRASER TON IMPOSSIBLE', 'FR', 15.5, 7.6, 'Auray', 'AY', 123, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE, 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (8, 'ABC000480070', '', 'TR0588', 'ZQ658496', 'DURER REJETER RECONNAÎTRE', 'FR', 15.5, 7.6, 'Auray', 'AY', 130, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE, 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (9, 'ABC000640738', '', 'IJU5217', 'EJ600773', 'PROFITER ESPRIT DEPUIS', 'FR', 15.5, 7.6, 'Auray', 'AY', 123, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE, 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (10, 'ABC000042310', '', 'IW783219', 'QD0506', 'COURANT MAIN PROFESSEUR', 'FR', 12.5, 7.6, 'Lorient', 'LO', 105, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE + INTERVAL '2 days', 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (11, 'ABC000597493', '', 'JL026591', 'CMQ7994', 'RENCONTRER VEILLER APPARTEMENT', 'FR', 15.5, 7.6, 'Auray', 'AY', 123, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE + INTERVAL '5 days', 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (12, 'FR263465414', '', 'IR123', '08FR65465', 'LE b@TO 2', 'FR', 15.5, 7.6, 'Auray', 'AY', 123, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE + INTERVAL '5 days', 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true), + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'), (13, 'FR263454484', '', 'FE4864', '8FR6541', 'NO NAME', 'FR', 15.5, 7.6, 'Auray', 'AY', 123, 'LORIENT', 108, 'Pêche côtière', '3', 'Pêche', ARRAY ['PTM', 'OTM'], CURRENT_DATE + INTERVAL '5 days', 'DUPOND', ARRAY ['+33 6 84 56 32 14'], 'dupond@gmail.com', 'DURAND', ARRAY ['+33 6 45 25 14'], ARRAY ['durand@gmail.com'], - ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true); + ARRAY ['escogriffe@dgse.spy', 'henri.duflot@dgse.spy'], ARRAY ['0918273645', '+33 6 00 00 00 00'], true, false, 'Equipé'); INSERT INTO vessels (id, cfr, ircs, external_immatriculation, vessel_name) VALUES (-1, 'UNKNOWN', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN'); diff --git a/backend/src/main/resources/db/testdata/V666.23__Insert_dummy_beacons.sql b/backend/src/main/resources/db/testdata/V666.23__Insert_dummy_beacons.sql index 5e30b15b55..0dc25c6f0d 100644 --- a/backend/src/main/resources/db/testdata/V666.23__Insert_dummy_beacons.sql +++ b/backend/src/main/resources/db/testdata/V666.23__Insert_dummy_beacons.sql @@ -1,12 +1,12 @@ INSERT INTO public.beacons ( - beacon_number, vessel_id, beacon_status, satellite_operator_id, logging_datetime_utc) + beacon_number, vessel_id, beacon_status, is_coastal, satellite_operator_id, logging_datetime_utc) VALUES - ( 'FGEDX85', 1, 'ACTIVATED', 1, '2021-5-12 12:23'), - ( '123456', 2, 'ACTIVATED', 1, '2021-4-22 22:25'), - ( 'ETETE4', 3, 'ACTIVATED', 1, '2021-3-20 12:38'), - ( 'A56CZ2', 4, 'ACTIVATED', 2, '2021-2-20 12:25'), - ( 'FEZFS65', 5, 'UNSUPERVISED', 2, '2021-1-12 10:47'), - ( 'LHGY122', 7, 'IN_TEST', 2, '2021-12-2 12:21'), - ( 'NB56FR8', 8, 'ACTIVATED', 2, '2021-6-2 9:23'), - ( 'PO8U9U4', 9, 'ACTIVATED', 2, '2021-5-2 12:27'), - ( 'ABC1234', null, 'ACTIVATED', 2, '2021-5-1 12:20'); + ( 'FGEDX85', 1, 'ACTIVATED', TRUE, 1, '2021-5-12 12:23'), + ( '123456', 2, 'ACTIVATED', FALSE, 1, '2021-4-22 22:25'), + ( 'ETETE4', 3, 'ACTIVATED', FALSE, 1, '2021-3-20 12:38'), + ( 'A56CZ2', 4, 'ACTIVATED', FALSE, 2, '2021-2-20 12:25'), + ( 'FEZFS65', 5, 'UNSUPERVISED', FALSE, 2, '2021-1-12 10:47'), + ( 'LHGY122', 7, 'IN_TEST', FALSE, 2, '2021-12-2 12:21'), + ( 'NB56FR8', 8, 'ACTIVATED', FALSE, 2, '2021-6-2 9:23'), + ( 'PO8U9U4', 9, 'ACTIVATED', FALSE, 2, '2021-5-2 12:27'), + ( 'ABC1234', null, 'ACTIVATED', FALSE, 2, '2021-5-1 12:20'); diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt new file mode 100644 index 0000000000..338e74c920 --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt @@ -0,0 +1,24 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.logbook + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class LogbookSoftwareUTests { + @Test + fun `isVisioCapture Should return False if the software is not under VisioCaptures`() { + // When + val isVisioCapture = LogbookSoftware.isVisioCapture("e-Sacapt") + + // Then + assertThat(isVisioCapture).isFalse() + } + + @Test + fun `isVisioCapture Should return True if the software is under VisioCaptures`() { + // When + val isVisioCapture = LogbookSoftware.isVisioCapture("FT-Visio...") + + // Then + assertThat(isVisioCapture).isTrue() + } +} diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt index 001a5c60f0..50a2718ec8 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/GetVesselUTests.kt @@ -125,6 +125,7 @@ class GetVesselUTests { listOf(firstPosition, fourthPosition, secondPosition, thirdPosition), ) given(vesselRepository.findVesselById(any())).willReturn(DUMMY_VESSEL) + given(logbookReportRepository.findLastReportSoftware(any())).willReturn("FT_E-Sacapt") given(riskFactorRepository.findByInternalReferenceNumber(any())).willReturn( VesselRiskFactor(2.3, 2.0, 1.9, 3.2), ) @@ -155,6 +156,7 @@ class GetVesselUTests { // Then assertThat(pair.first).isFalse assertThat(pair.second.vessel?.id).isEqualTo(123) + assertThat(pair.second.vessel?.hasVisioCaptures).isTrue() assertThat(pair.second.beacon?.beaconNumber).isEqualTo("A_BEACON_NUMBER") assertThat(pair.second.positions.first().dateTime).isEqualTo(now.minusHours(4)) assertThat(pair.second.positions.last().dateTime).isEqualTo(now.minusHours(1)) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt index 3a74e1a262..78cbbaeb96 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/TestUtils.kt @@ -13,7 +13,12 @@ import java.time.ZoneOffset.UTC import java.time.ZonedDateTime object TestUtils { - val DUMMY_VESSEL = Vessel(id = 123, flagState = CountryCode.FR, hasLogbookEsacapt = false) + val DUMMY_VESSEL = Vessel( + id = 123, + internalReferenceNumber = "DUMMY_CFR", + flagState = CountryCode.FR, + hasLogbookEsacapt = false, + ) fun getDummyReportings(): List { return listOf( diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt index f0837b4eca..e440da6f0e 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaLogbookReportRepositoryITests.kt @@ -1112,6 +1112,26 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { assertThat(fifthRelatedLogbookReportModels.count { it.operationType == LogbookOperationType.RET }).isEqualTo(1) } + @Test + @Transactional + fun `findLastReportSoftware Should return the software of the last message`() { + // When + val software = jpaLogbookReportRepository.findLastReportSoftware("U_W0NTFINDME") + + // Then + assertThat(software).isEqualTo("TurboCatch (3.7-1)") + } + + @Test + @Transactional + fun `findLastReportSoftware Should return null if no message found`() { + // When + val software = jpaLogbookReportRepository.findLastReportSoftware("UNKNOWN_CFR") + + // Then + assertThat(software).isNull() + } + companion object { private fun getFakeLogbookReportModel( operationType: LogbookOperationType, From 0ad85e9ddeb9f8c44dc9e57dfc9433eb6030126f Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Mon, 3 Jun 2024 17:48:48 +0200 Subject: [PATCH 6/9] Display VMS and ERS equipments is vessel sidebar --- .../entities/authorization/constants.ts | 2 +- .../entities/beaconMalfunction/constants.tsx | 6 +- .../domain/shared_slices/BeaconMalfunction.ts | 10 +- frontend/src/features/Vessel/Vessel.types.ts | 12 +- frontend/src/features/VesselSidebar/Body.tsx | 4 +- .../VesselEquipment.tsx} | 18 +- .../details/BeaconMalfunctionDetails.tsx | 4 +- .../resume/BeaconMalfunctionBody.tsx | 0 .../resume/BeaconMalfunctionCard.tsx | 0 .../resume/BeaconMalfunctionsResume.tsx | 0 .../resume/CurrentBeaconMalfunction.tsx | 0 .../resume/CurrentBeaconMalfunctionBody.tsx | 0 .../Equipment/resume/EquipmentResume.tsx | 145 ++++++++++++++++ .../resume/YearBeaconMalfunctions.tsx | 0 .../resume/YearsToBeaconMalfunctionList.tsx | 0 .../features/VesselSidebar/Identity/index.tsx | 162 +++++++----------- frontend/src/features/VesselSidebar/Tabs.tsx | 18 +- .../BeaconMalfunctionsResumeAndHistory.tsx | 97 ----------- .../common/TwoColumnKeyValueTable.tsx | 111 ++++++++++++ 19 files changed, 355 insertions(+), 234 deletions(-) rename frontend/src/features/VesselSidebar/{beacon_malfunctions/VesselBeaconMalfunctions.tsx => Equipment/VesselEquipment.tsx} (86%) rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/details/BeaconMalfunctionDetails.tsx (96%) rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/resume/BeaconMalfunctionBody.tsx (100%) rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/resume/BeaconMalfunctionCard.tsx (100%) rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/resume/BeaconMalfunctionsResume.tsx (100%) rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/resume/CurrentBeaconMalfunction.tsx (100%) rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/resume/CurrentBeaconMalfunctionBody.tsx (100%) create mode 100644 frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/resume/YearBeaconMalfunctions.tsx (100%) rename frontend/src/features/VesselSidebar/{beacon_malfunctions => Equipment}/resume/YearsToBeaconMalfunctionList.tsx (100%) delete mode 100644 frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionsResumeAndHistory.tsx create mode 100644 frontend/src/features/VesselSidebar/common/TwoColumnKeyValueTable.tsx diff --git a/frontend/src/domain/entities/authorization/constants.ts b/frontend/src/domain/entities/authorization/constants.ts index 668709a6a5..b6871fc61e 100644 --- a/frontend/src/domain/entities/authorization/constants.ts +++ b/frontend/src/domain/entities/authorization/constants.ts @@ -1,3 +1,3 @@ import { VesselSidebarTab } from '../vessel/vessel' -export const forbiddenVesselSidebarPaths = [VesselSidebarTab.REPORTING, VesselSidebarTab.ERSVMS] +export const forbiddenVesselSidebarPaths = [VesselSidebarTab.REPORTING] diff --git a/frontend/src/domain/entities/beaconMalfunction/constants.tsx b/frontend/src/domain/entities/beaconMalfunction/constants.tsx index fe2565729a..680cffbae1 100644 --- a/frontend/src/domain/entities/beaconMalfunction/constants.tsx +++ b/frontend/src/domain/entities/beaconMalfunction/constants.tsx @@ -13,8 +13,8 @@ import type { EnfOfBeaconMalfunctionStatusValue } from './types' -enum BeaconMalfunctionsTab { - DETAIL = 2, +enum EquipmentTab { + BEACON_MALFUNCTION_DETAIL = 2, RESUME = 1 } @@ -294,7 +294,7 @@ export { BeaconMalfunctionPropertyName, BeaconMalfunctionVesselStatus, VESSEL_STATUS, - BeaconMalfunctionsTab, + EquipmentTab, END_OF_MALFUNCTION_REASON_RECORD, STAGE_RECORD, NOTIFICATION_TYPE, diff --git a/frontend/src/domain/shared_slices/BeaconMalfunction.ts b/frontend/src/domain/shared_slices/BeaconMalfunction.ts index 72b9770329..acec58bb27 100644 --- a/frontend/src/domain/shared_slices/BeaconMalfunction.ts +++ b/frontend/src/domain/shared_slices/BeaconMalfunction.ts @@ -1,6 +1,6 @@ import { createSlice } from '@reduxjs/toolkit' -import { BeaconMalfunctionsTab } from '../entities/beaconMalfunction/constants' +import { EquipmentTab } from '../entities/beaconMalfunction/constants' import type { BeaconMalfunction, @@ -13,7 +13,7 @@ import type { ValueOf } from 'type-fest' export type BeaconMalfunctionState = { beaconMalfunctions: BeaconMalfunction[] // TODO Replace when converted to enum. - beaconMalfunctionsTab: ValueOf + beaconMalfunctionsTab: ValueOf loadingVesselBeaconMalfunctions: boolean openedBeaconMalfunction: BeaconMalfunctionResumeAndDetails | null openedBeaconMalfunctionInKanban: BeaconMalfunctionResumeAndDetails | null @@ -22,7 +22,7 @@ export type BeaconMalfunctionState = { } const INITIAL_STATE: BeaconMalfunctionState = { beaconMalfunctions: [], - beaconMalfunctionsTab: BeaconMalfunctionsTab.RESUME, + beaconMalfunctionsTab: EquipmentTab.RESUME, loadingVesselBeaconMalfunctions: false, openedBeaconMalfunction: null, openedBeaconMalfunctionInKanban: null, @@ -52,7 +52,7 @@ const beaconMalfunctionSlice = createSlice({ state.loadingVesselBeaconMalfunctions = true state.openedBeaconMalfunction = null state.vesselBeaconMalfunctionsResumeAndHistory = null - state.beaconMalfunctionsTab = BeaconMalfunctionsTab.RESUME + state.beaconMalfunctionsTab = EquipmentTab.RESUME }, /** @@ -99,7 +99,7 @@ const beaconMalfunctionSlice = createSlice({ state.openedBeaconMalfunction = action.payload.beaconMalfunction if (action.payload.showTab) { - state.beaconMalfunctionsTab = BeaconMalfunctionsTab.DETAIL + state.beaconMalfunctionsTab = EquipmentTab.BEACON_MALFUNCTION_DETAIL } }, diff --git a/frontend/src/features/Vessel/Vessel.types.ts b/frontend/src/features/Vessel/Vessel.types.ts index 29c2625996..8306f04e0f 100644 --- a/frontend/src/features/Vessel/Vessel.types.ts +++ b/frontend/src/features/Vessel/Vessel.types.ts @@ -1,19 +1,27 @@ import type { RiskFactor } from '../../domain/entities/vessel/riskFactor/types' export namespace Vessel { + export type Beacon = { + beaconNumber: string + isCoastal: string | undefined + loggingDatetimeUtc: string | undefined + } + export type Vessel = { - beaconNumber: number | undefined + beacon: Beacon declaredFishingGears: string[] | undefined district: string | undefined districtCode: string | undefined externalReferenceNumber: string | undefined - // TODO What's the value for "Navire inconnu"? flagState: string gauge: number | undefined + hasLogbookEsacapt: boolean + hasVisioCaptures: boolean | undefined imo: string | undefined internalReferenceNumber: string | undefined ircs: string | undefined length: number | undefined + logbookEquipmentStatus: string | undefined mmsi: string | undefined navigationLicenceExpirationDate: string | undefined operatorEmails: string[] | undefined diff --git a/frontend/src/features/VesselSidebar/Body.tsx b/frontend/src/features/VesselSidebar/Body.tsx index 759e1875fe..430b551c31 100644 --- a/frontend/src/features/VesselSidebar/Body.tsx +++ b/frontend/src/features/VesselSidebar/Body.tsx @@ -3,8 +3,8 @@ import { DisplayedErrorKey } from '@libs/DisplayedError/constants' import { Accent, Button } from '@mtes-mct/monitor-ui' import styled from 'styled-components' -import { VesselBeaconMalfunctions } from './beacon_malfunctions/VesselBeaconMalfunctions' import { Controls } from './Controls' +import { VesselEquipment } from './Equipment/VesselEquipment' import { Identity } from './Identity' import { VesselSummary } from './Summary' import { AlertWarning } from './warnings/AlertWarning' @@ -51,7 +51,7 @@ export function Body() { {vesselSidebarTab === VesselSidebarTab.VOYAGES && } {vesselSidebarTab === VesselSidebarTab.CONTROLS && } {isSuperUser && vesselSidebarTab === VesselSidebarTab.REPORTING && } - {isSuperUser && vesselSidebarTab === VesselSidebarTab.ERSVMS && } + {vesselSidebarTab === VesselSidebarTab.ERSVMS && } ) } diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/VesselBeaconMalfunctions.tsx b/frontend/src/features/VesselSidebar/Equipment/VesselEquipment.tsx similarity index 86% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/VesselBeaconMalfunctions.tsx rename to frontend/src/features/VesselSidebar/Equipment/VesselEquipment.tsx index 1c6f3083a6..e720960d54 100644 --- a/frontend/src/features/VesselSidebar/beacon_malfunctions/VesselBeaconMalfunctions.tsx +++ b/frontend/src/features/VesselSidebar/Equipment/VesselEquipment.tsx @@ -4,17 +4,19 @@ import { FingerprintSpinner } from 'react-epic-spinners' import styled from 'styled-components' import { BeaconMalfunctionDetails } from './details/BeaconMalfunctionDetails' -import { BeaconMalfunctionsResumeAndHistory } from './resume/BeaconMalfunctionsResumeAndHistory' +import { EquipmentResume } from './resume/EquipmentResume' +import { useIsSuperUser } from '../../../auth/hooks/useIsSuperUser' import { COLORS } from '../../../constants/constants' -import { BeaconMalfunctionsTab } from '../../../domain/entities/beaconMalfunction/constants' +import { EquipmentTab } from '../../../domain/entities/beaconMalfunction/constants' import { vesselsAreEquals } from '../../../domain/entities/vessel/vessel' import { setBeaconMalfunctionsTab } from '../../../domain/shared_slices/BeaconMalfunction' import { getVesselBeaconMalfunctions } from '../../../domain/use_cases/beaconMalfunction/getVesselBeaconMalfunctions' import { useMainAppDispatch } from '../../../hooks/useMainAppDispatch' import { useMainAppSelector } from '../../../hooks/useMainAppSelector' -export function VesselBeaconMalfunctions() { +export function VesselEquipment() { const dispatch = useMainAppDispatch() + const isSuperUser = useIsSuperUser() const { beaconMalfunctionsTab, loadingVesselBeaconMalfunctions, @@ -32,7 +34,7 @@ export function VesselBeaconMalfunctions() { useEffect(() => { if (!vesselsAreEquals(previousSelectedVesselIdentity, selectedVesselIdentity)) { - dispatch(setBeaconMalfunctionsTab(BeaconMalfunctionsTab.RESUME)) + dispatch(setBeaconMalfunctionsTab(EquipmentTab.RESUME)) } }, [dispatch, selectedVesselIdentity, vesselBeaconMalfunctionsFromDate, previousSelectedVesselIdentity]) @@ -48,12 +50,10 @@ export function VesselBeaconMalfunctions() { return ( - {beaconMalfunctionsTab === BeaconMalfunctionsTab.RESUME && ( - + {beaconMalfunctionsTab === EquipmentTab.RESUME && ( + )} - {beaconMalfunctionsTab === BeaconMalfunctionsTab.DETAIL && ( + {isSuperUser && beaconMalfunctionsTab === EquipmentTab.BEACON_MALFUNCTION_DETAIL && ( )} diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/details/BeaconMalfunctionDetails.tsx b/frontend/src/features/VesselSidebar/Equipment/details/BeaconMalfunctionDetails.tsx similarity index 96% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/details/BeaconMalfunctionDetails.tsx rename to frontend/src/features/VesselSidebar/Equipment/details/BeaconMalfunctionDetails.tsx index d99eea5987..b0cc7ec9ea 100644 --- a/frontend/src/features/VesselSidebar/beacon_malfunctions/details/BeaconMalfunctionDetails.tsx +++ b/frontend/src/features/VesselSidebar/Equipment/details/BeaconMalfunctionDetails.tsx @@ -2,7 +2,7 @@ import styled from 'styled-components' import { COLORS } from '../../../../constants/constants' import { getFirstVesselStatus } from '../../../../domain/entities/beaconMalfunction' -import { BeaconMalfunctionsTab } from '../../../../domain/entities/beaconMalfunction/constants' +import { EquipmentTab } from '../../../../domain/entities/beaconMalfunction/constants' import { setBeaconMalfunctionsTab } from '../../../../domain/shared_slices/BeaconMalfunction' import { useMainAppDispatch } from '../../../../hooks/useMainAppDispatch' import { useMainAppSelector } from '../../../../hooks/useMainAppSelector' @@ -19,7 +19,7 @@ export function BeaconMalfunctionDetails({ isCurrentBeaconMalfunctionDetails }: const openedBeaconMalfunction = useMainAppSelector(state => state.beaconMalfunction.openedBeaconMalfunction) const dispatch = useMainAppDispatch() - const navigateToResume = () => dispatch(setBeaconMalfunctionsTab(BeaconMalfunctionsTab.RESUME)) + const navigateToResume = () => dispatch(setBeaconMalfunctionsTab(EquipmentTab.RESUME)) return ( diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionBody.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/BeaconMalfunctionBody.tsx similarity index 100% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionBody.tsx rename to frontend/src/features/VesselSidebar/Equipment/resume/BeaconMalfunctionBody.tsx diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionCard.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/BeaconMalfunctionCard.tsx similarity index 100% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionCard.tsx rename to frontend/src/features/VesselSidebar/Equipment/resume/BeaconMalfunctionCard.tsx diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionsResume.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/BeaconMalfunctionsResume.tsx similarity index 100% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionsResume.tsx rename to frontend/src/features/VesselSidebar/Equipment/resume/BeaconMalfunctionsResume.tsx diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/CurrentBeaconMalfunction.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/CurrentBeaconMalfunction.tsx similarity index 100% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/resume/CurrentBeaconMalfunction.tsx rename to frontend/src/features/VesselSidebar/Equipment/resume/CurrentBeaconMalfunction.tsx diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/CurrentBeaconMalfunctionBody.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/CurrentBeaconMalfunctionBody.tsx similarity index 100% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/resume/CurrentBeaconMalfunctionBody.tsx rename to frontend/src/features/VesselSidebar/Equipment/resume/CurrentBeaconMalfunctionBody.tsx diff --git a/frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx new file mode 100644 index 0000000000..52e1d7560b --- /dev/null +++ b/frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx @@ -0,0 +1,145 @@ +import { TwoColumnKeyValueTable } from '@features/VesselSidebar/common/TwoColumnKeyValueTable' +import { useMainAppDispatch } from '@hooks/useMainAppDispatch' +import { useMainAppSelector } from '@hooks/useMainAppSelector' +import { THEME } from '@mtes-mct/monitor-ui' +import { useMemo } from 'react' +import { FingerprintSpinner } from 'react-epic-spinners' +import styled from 'styled-components' + +import { BeaconMalfunctionsResume } from './BeaconMalfunctionsResume' +import { CurrentBeaconMalfunction } from './CurrentBeaconMalfunction' +import { YearsToBeaconMalfunctionList } from './YearsToBeaconMalfunctionList' +import { useIsSuperUser } from '../../../../auth/hooks/useIsSuperUser' +import { getYearsToBeaconMalfunctions } from '../../../../domain/entities/beaconMalfunction' +import { setVesselBeaconMalfunctionsFromDate } from '../../../../domain/shared_slices/BeaconMalfunction' +import { getDateTime } from '../../../../utils' + +import type { Promisable } from 'type-fest' + +type BeaconMalfunctionsResumeAndHistoryProps = { + setIsCurrentBeaconMalfunctionDetails: (isCurrentBeaconMalfunctionDetails: boolean) => Promisable +} +export function EquipmentResume({ setIsCurrentBeaconMalfunctionDetails }: BeaconMalfunctionsResumeAndHistoryProps) { + const dispatch = useMainAppDispatch() + const isSuperUser = useIsSuperUser() + + const loadingVesselBeaconMalfunctions = useMainAppSelector( + state => state.beaconMalfunction.loadingVesselBeaconMalfunctions + ) + const vesselBeaconMalfunctionsFromDate = useMainAppSelector( + state => state.beaconMalfunction.vesselBeaconMalfunctionsFromDate + ) + const vesselBeaconMalfunctionsResumeAndHistory = useMainAppSelector( + state => state.beaconMalfunction.vesselBeaconMalfunctionsResumeAndHistory + ) + const selectedVessel = useMainAppSelector(state => state.vessel.selectedVessel) + + const yearsToBeaconMalfunctions = useMemo(() => { + if (!vesselBeaconMalfunctionsResumeAndHistory?.history) { + return {} + } + + return getYearsToBeaconMalfunctions( + vesselBeaconMalfunctionsFromDate, + vesselBeaconMalfunctionsResumeAndHistory.history + ) + }, [vesselBeaconMalfunctionsResumeAndHistory?.history, vesselBeaconMalfunctionsFromDate]) + + const seeMore = () => { + const nextDate = new Date(vesselBeaconMalfunctionsFromDate.getTime()) + nextDate.setMonth(nextDate.getMonth() - 12) + + dispatch(setVesselBeaconMalfunctionsFromDate(nextDate)) + } + + return ( + <> + {!loadingVesselBeaconMalfunctions ? ( + + + {isSuperUser && ( + <> + + + + + Afficher plus d'avaries + + + )} + + ) : ( + + )} + + ) +} + +const StyledTwoColumnKeyValueTable = styled(TwoColumnKeyValueTable)` + margin-top: 10px; +` + +const SeeMoreBackground = styled.div` + background: ${p => p.theme.color.white}; + margin: 0px 5px 10px 5px; + padding: 5px 0 5px 0; +` + +const SeeMore = styled.div` + border: 1px solid ${p => p.theme.color.charcoal}; + color: ${p => p.theme.color.gunMetal}; + padding: 5px 10px 5px 10px; + width: max-content; + font-size: 13px; + cursor: pointer; + margin-left: auto; + margin-right: auto; + user-select: none; + background: ${p => p.theme.color.white}; +` + +const Body = styled.div` + padding: 0 5px 1px 5px; + overflow-x: hidden; + max-height: 700px; +` diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/YearBeaconMalfunctions.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/YearBeaconMalfunctions.tsx similarity index 100% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/resume/YearBeaconMalfunctions.tsx rename to frontend/src/features/VesselSidebar/Equipment/resume/YearBeaconMalfunctions.tsx diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/YearsToBeaconMalfunctionList.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/YearsToBeaconMalfunctionList.tsx similarity index 100% rename from frontend/src/features/VesselSidebar/beacon_malfunctions/resume/YearsToBeaconMalfunctionList.tsx rename to frontend/src/features/VesselSidebar/Equipment/resume/YearsToBeaconMalfunctionList.tsx diff --git a/frontend/src/features/VesselSidebar/Identity/index.tsx b/frontend/src/features/VesselSidebar/Identity/index.tsx index 3729e9cbea..ac2c378871 100644 --- a/frontend/src/features/VesselSidebar/Identity/index.tsx +++ b/frontend/src/features/VesselSidebar/Identity/index.tsx @@ -1,3 +1,4 @@ +import { TwoColumnKeyValueTable } from '@features/VesselSidebar/common/TwoColumnKeyValueTable' import countries from 'i18n-iso-countries' import { useEffect, useMemo } from 'react' import { FingerprintSpinner } from 'react-epic-spinners' @@ -60,94 +61,64 @@ export function Identity() { return !loadingVessel ? ( - - - - - CFR - {getVesselOrLastPositionProperty('internalReferenceNumber')} - - - MMSI - {getVesselOrLastPositionProperty('mmsi')} - - - Balise n° - {getVesselOrLastPositionProperty('beaconNumber')} - - - - - - - Marquage ext. - {getVesselOrLastPositionProperty('externalReferenceNumber')} - - - Call Sign (IRCS) - {getVesselOrLastPositionProperty('ircs')} - - - - - - - - - - - - - Nationalité - - {selectedVessel?.flagState && countries.getName(selectedVessel?.flagState, 'fr') ? ( - countries.getName(selectedVessel?.flagState, 'fr') - ) : ( - - - )} - - - - Quartier - - {selectedVessel?.district ? ( - <> - {selectedVessel?.district}{' '} - {selectedVessel?.districtCode ? <>({selectedVessel?.districtCode}) : ''} - - ) : ( - - - )} - - - - Port d'attache - - {selectedVessel?.registryPort ? selectedVessel?.registryPort : -} - - - - - - - - Taille - - {selectedVessel?.length ? selectedVessel?.length : -} x{' '} - {selectedVessel?.width ? selectedVessel?.width : -} - - - - Jauge - {selectedVessel?.gauge ? <>{selectedVessel?.gauge} GT : -} - - - Moteur - {selectedVessel?.power ? <>{selectedVessel?.power} kW : -} - - - - + + @@ -378,21 +349,6 @@ const Key = styled.th` font-weight: normal; ` -const TrimmedValue = styled.td` - font-size: 13px; - color: ${p => p.theme.color.gunMetal}; - text-align: left; - padding: 1px 5px 5px 5px; - background: none; - border: none; - line-height: normal; - text-overflow: ellipsis; - overflow: hidden !important; - white-space: nowrap; - max-width: 120px; - font-weight: 500; -` - const Value = styled.td` font-size: 13px; color: ${p => p.theme.color.gunMetal}; diff --git a/frontend/src/features/VesselSidebar/Tabs.tsx b/frontend/src/features/VesselSidebar/Tabs.tsx index 88e95c98c2..8b74fdb249 100644 --- a/frontend/src/features/VesselSidebar/Tabs.tsx +++ b/frontend/src/features/VesselSidebar/Tabs.tsx @@ -79,16 +79,14 @@ export function Tabs() { >
Contrôles - {isSuperUser && ( - showTab(VesselSidebarTab.ERSVMS)} - > -
VMS -
- )} + showTab(VesselSidebarTab.ERSVMS)} + > +
VMS/JPE +
) } diff --git a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionsResumeAndHistory.tsx b/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionsResumeAndHistory.tsx deleted file mode 100644 index 13695aaa8d..0000000000 --- a/frontend/src/features/VesselSidebar/beacon_malfunctions/resume/BeaconMalfunctionsResumeAndHistory.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useMemo } from 'react' -import { FingerprintSpinner } from 'react-epic-spinners' -import styled from 'styled-components' - -import { BeaconMalfunctionsResume } from './BeaconMalfunctionsResume' -import { CurrentBeaconMalfunction } from './CurrentBeaconMalfunction' -import { YearsToBeaconMalfunctionList } from './YearsToBeaconMalfunctionList' -import { COLORS } from '../../../../constants/constants' -import { getYearsToBeaconMalfunctions } from '../../../../domain/entities/beaconMalfunction' -import { setVesselBeaconMalfunctionsFromDate } from '../../../../domain/shared_slices/BeaconMalfunction' -import { useMainAppDispatch } from '../../../../hooks/useMainAppDispatch' -import { useMainAppSelector } from '../../../../hooks/useMainAppSelector' - -import type { Promisable } from 'type-fest' - -type BeaconMalfunctionsResumeAndHistoryProps = { - setIsCurrentBeaconMalfunctionDetails: (isCurrentBeaconMalfunctionDetails: boolean) => Promisable -} -export function BeaconMalfunctionsResumeAndHistory({ - setIsCurrentBeaconMalfunctionDetails -}: BeaconMalfunctionsResumeAndHistoryProps) { - const dispatch = useMainAppDispatch() - - const { - loadingVesselBeaconMalfunctions, - vesselBeaconMalfunctionsFromDate, - vesselBeaconMalfunctionsResumeAndHistory - } = useMainAppSelector(state => state.beaconMalfunction) - - /** @type {Object.} yearsToBeaconMalfunctions */ - const yearsToBeaconMalfunctions = useMemo(() => { - if (!vesselBeaconMalfunctionsResumeAndHistory?.history) { - return {} - } - - return getYearsToBeaconMalfunctions( - vesselBeaconMalfunctionsFromDate, - vesselBeaconMalfunctionsResumeAndHistory.history - ) - }, [vesselBeaconMalfunctionsResumeAndHistory?.history, vesselBeaconMalfunctionsFromDate]) - - const seeMore = () => { - const nextDate = new Date(vesselBeaconMalfunctionsFromDate.getTime()) - nextDate.setMonth(nextDate.getMonth() - 12) - - dispatch(setVesselBeaconMalfunctionsFromDate(nextDate)) - } - - return ( - <> - {!loadingVesselBeaconMalfunctions ? ( - - - - - - Afficher plus d'avaries - - - ) : ( - - )} - - ) -} - -const SeeMoreBackground = styled.div` - background: ${p => p.theme.color.white}; - margin: 0px 5px 10px 5px; - padding: 5px 0 5px 0; -` - -const SeeMore = styled.div` - border: 1px solid ${p => p.theme.color.charcoal}; - color: ${p => p.theme.color.gunMetal}; - padding: 5px 10px 5px 10px; - width: max-content; - font-size: 13px; - cursor: pointer; - margin-left: auto; - margin-right: auto; - user-select: none; - background: ${p => p.theme.color.white}; -` - -const Body = styled.div` - padding: 0 5px 1px 5px; - overflow-x: hidden; - max-height: 700px; -` diff --git a/frontend/src/features/VesselSidebar/common/TwoColumnKeyValueTable.tsx b/frontend/src/features/VesselSidebar/common/TwoColumnKeyValueTable.tsx new file mode 100644 index 0000000000..e6a993300a --- /dev/null +++ b/frontend/src/features/VesselSidebar/common/TwoColumnKeyValueTable.tsx @@ -0,0 +1,111 @@ +import { Header } from '@features/VesselSidebar/common_styles/common.style' +import { range } from 'lodash' +import styled from 'styled-components' + +type KeyValue = { + key: string + value: boolean | string | number | undefined +} + +type KeyValueTableProps = { + className?: string | undefined + firstColumn: Array + header?: string | undefined + secondColumn: Array +} +export function TwoColumnKeyValueTable({ className, firstColumn, header, secondColumn }: KeyValueTableProps) { + return ( + + {!!header &&
{header}
} + + + {firstColumn.map(({ key, value }) => ( + + {key} + {value ? {value} : -} + + ))} + + + + + {secondColumn.map(({ key, value }) => ( + + {key} + {value ? {value} : -} + + ))} + {/** We add empty item in second column to to not break the first column */} + {secondColumn.length < firstColumn.length && + range(firstColumn.length - secondColumn.length).map(() => ( + + + + + ))} + + +
+ ) +} + +const TableBody = styled.tbody`` + +const Zone = styled.div` + background: ${p => p.theme.color.white}; + display: flex; + flex-wrap: wrap; + margin: 5px 5px 10px; + text-align: left; + + > table:not(:first-child) { + margin-left: 25px; + } +` + +const Fields = styled.table<{ + isSecondColumn?: boolean +}>` + padding: 10px 5px 5px 20px; + margin: 10px ${p => (p.isSecondColumn ? 20 : 0)}px 10px ${p => (p.isSecondColumn ? 0 : 20)}px; + width: inherit; + display: table; + min-width: 40%; +` + +const Field = styled.tr` + margin: 5px 5px 5px 0; + border: none; + background: none; + line-height: 0.5em; +` + +const Key = styled.th` + color: ${p => p.theme.color.slateGray}; + flex: initial; + display: inline-block; + padding: 1px 5px 5px 0; + width: max-content; + line-height: 0.5em; + font-weight: normal; +` + +const Value = styled.td` + color: ${p => p.theme.color.gunMetal}; + padding: 1px 5px 5px 5px; + line-height: normal; + text-overflow: ellipsis; + overflow: hidden !important; + white-space: nowrap; + height: 19px; + max-width: 100px; + font-weight: 500; +` + +const NoValue = styled.td` + padding: 1px 5px 5px 5px; + line-height: normal; + color: ${p => p.theme.color.slateGray}; + font-weight: 300; + line-height: normal; +` From b5eb67bf9b89761a137675ffe166eb2bdd8e472a Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Tue, 4 Jun 2024 10:29:58 +0200 Subject: [PATCH 7/9] Add cypress test --- .../e2e/vessel_sidebar/ers_vms.spec.ts | 32 +++++++- .../Equipment/resume/EquipmentResume.tsx | 79 ++++++++++-------- .../features/VesselSidebar/Identity/index.tsx | 6 +- frontend/src/features/VesselSidebar/Tabs.tsx | 8 +- .../VesselSidebar/common/FlatKeyValue.tsx | 80 +++++++++++++++++++ ...lueTable.tsx => FlatTwoColumnKeyValue.tsx} | 5 +- 6 files changed, 163 insertions(+), 47 deletions(-) create mode 100644 frontend/src/features/VesselSidebar/common/FlatKeyValue.tsx rename frontend/src/features/VesselSidebar/common/{TwoColumnKeyValueTable.tsx => FlatTwoColumnKeyValue.tsx} (90%) diff --git a/frontend/cypress/e2e/vessel_sidebar/ers_vms.spec.ts b/frontend/cypress/e2e/vessel_sidebar/ers_vms.spec.ts index 732cd24c8e..ffc64c63f4 100644 --- a/frontend/cypress/e2e/vessel_sidebar/ers_vms.spec.ts +++ b/frontend/cypress/e2e/vessel_sidebar/ers_vms.spec.ts @@ -5,6 +5,32 @@ context('Vessel sidebar ers/vms tab', () => { cy.loadPath('/#@-824534.42,6082993.21,8.70') }) + it('ERS/VMS tab Should show information about vessel equipment', () => { + // Given + cy.get('input[placeholder="Rechercher un navire..."]').type('pheno') + cy.contains('mark', 'PHENO').click() + cy.wait(50) + cy.get('*[data-cy="vessel-sidebar"]', { timeout: 10000 }).should('be.visible') + cy.intercept('GET', '/bff/v1/vessels/beacon_malfunctions*').as('vesselBeaconMalfunctions') + + // When + cy.get('*[data-cy="vessel-menu-ers-vms"]').click({ timeout: 10000 }) + + // Then + cy.get('*[data-cy="vessel-equipments"]').contains('N° balise VMS') + cy.get('*[data-cy="vessel-equipments"]').contains('FGEDX85') + cy.get('*[data-cy="vessel-equipments"]').contains('Type de balise') + cy.get('*[data-cy="vessel-equipments"]').contains('Côtier') + cy.get('*[data-cy="vessel-equipments"]').contains('Date de loggage') + cy.get('*[data-cy="vessel-equipments"]').contains('12/05/2021 à 12h23:00') + cy.get('*[data-cy="vessel-equipments"]').contains('Statut JPE') + cy.get('*[data-cy="vessel-equipments"]').contains('Equipé') + cy.get('*[data-cy="vessel-equipments"]').contains('Équipé e-Sacapt') + cy.get('*[data-cy="vessel-equipments"]').contains('Non') + cy.get('*[data-cy="vessel-equipments"]').contains('Équipé VisioCaptures') + cy.get('*[data-cy="vessel-equipments"]').contains('Non') + }) + it('ERS/VMS tab Should contain history of beacon malfunctions and show a malfunction detail in history', () => { // Given cy.get('.VESSELS_POINTS').click(460, 460, { force: true, timeout: 10000 }) @@ -15,7 +41,7 @@ context('Vessel sidebar ers/vms tab', () => { // When cy.get('*[data-cy="vessel-menu-ers-vms"]').click({ timeout: 10000 }) cy.get('*[data-cy="vessel-beacon-malfunctions"]', { timeout: 10000 }).should('be.visible') - cy.get('*[data-cy="vessel-malfunctions-resume"]', { timeout: 10000 }).should('be.visible') + cy.get('*[data-cy="vessel-equipments"]', { timeout: 10000 }).should('be.visible') // Then cy.wait('@vesselBeaconMalfunctions').then(({ response }) => expect(response && response.statusCode).equal(200)) @@ -63,7 +89,7 @@ context('Vessel sidebar ers/vms tab', () => { cy.get('*[data-cy="vessel-beacon-malfunctions-history"]', { timeout: 10000 }).children().eq(0).click() cy.get('*[data-cy="vessel-beacon-malfunction-single-history"]', { timeout: 10000 }).click({ force: true }) cy.get('*[data-cy="beacon-malfunction-back-to-resume"]', { timeout: 10000 }).click() - cy.get('*[data-cy="vessel-malfunctions-resume"]', { timeout: 10000 }).should('be.visible') + cy.get('*[data-cy="vessel-equipments"]', { timeout: 10000 }).should('be.visible') // See current beacon malfunction cy.get('*[data-cy="beacon-malfunction-current-see-details"]', { timeout: 10000 }).click() @@ -85,7 +111,7 @@ context('Vessel sidebar ers/vms tab', () => { cy.wait('@vesselTwoBeaconMalfunctions').then(({ response }) => expect(response && response.statusCode).equal(200)) // Then - cy.get('*[data-cy="vessel-malfunctions-resume"]', { timeout: 10000 }).should('be.visible') + cy.get('*[data-cy="vessel-equipments"]', { timeout: 10000 }).should('be.visible') cy.get('*[data-cy="vessel-beacon-malfunctions"]', { timeout: 10000 }).should('be.visible') cy.get('*[data-cy="vessel-beacon-malfunctions-resume-number"]', { timeout: 10000 }).contains('à quai 0') cy.get('*[data-cy="vessel-beacon-malfunctions-resume-number"]', { timeout: 10000 }).contains('en mer 1') diff --git a/frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx b/frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx index 52e1d7560b..215a511687 100644 --- a/frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx +++ b/frontend/src/features/VesselSidebar/Equipment/resume/EquipmentResume.tsx @@ -1,4 +1,4 @@ -import { TwoColumnKeyValueTable } from '@features/VesselSidebar/common/TwoColumnKeyValueTable' +import { FlatKeyValue } from '@features/VesselSidebar/common/FlatKeyValue' import { useMainAppDispatch } from '@hooks/useMainAppDispatch' import { useMainAppSelector } from '@hooks/useMainAppSelector' import { THEME } from '@mtes-mct/monitor-ui' @@ -55,39 +55,42 @@ export function EquipmentResume({ setIsCurrentBeaconMalfunctionDetails }: Beacon return ( <> {!loadingVesselBeaconMalfunctions ? ( - - + + + + + {isSuperUser && ( <> - - (!props.isLast ? `border-right: 1px solid ${COLORS.lightGray};` : null)} background: ${props => (props.isActive ? props.theme.color.blueGray : props.theme.color.charcoal)}; color: ${props => (props.isActive ? props.theme.color.white : props.theme.color.lightGray)}; - :hover, - :focus, + :focus { + color: ${p => p.theme.color.white}; + background: ${p => p.theme.color.blueYonder}; + ${props => (!props.isLast ? `border-right: 1px solid ${COLORS.lightGray};` : null)} + } + :active { color: ${p => p.theme.color.white}; background: ${p => p.theme.color.blueGray}; diff --git a/frontend/src/features/VesselSidebar/common/FlatKeyValue.tsx b/frontend/src/features/VesselSidebar/common/FlatKeyValue.tsx new file mode 100644 index 0000000000..b4e8c1386b --- /dev/null +++ b/frontend/src/features/VesselSidebar/common/FlatKeyValue.tsx @@ -0,0 +1,80 @@ +import styled from 'styled-components' + +type KeyValue = { + key: string + value: boolean | string | number | undefined +} + +type KeyValueTableProps = { + className?: string | undefined + column: Array +} +export function FlatKeyValue({ className, column }: KeyValueTableProps) { + return ( + + + + {column.map(({ key, value }) => ( + + {key} + {value ? {value} : -} + + ))} + + + + ) +} + +const TableBody = styled.tbody`` + +const Zone = styled.div` + background: ${p => p.theme.color.white}; + margin: 5px 5px 0px; + text-align: left; +` + +const Fields = styled.table<{ + isSecondColumn?: boolean +}>` + margin: 10px 0px 10px 16px; + display: table; + min-width: 40%; +` + +const Field = styled.tr` + margin: 5px 5px 5px 0; + border: none; + background: none; + line-height: 0.5em; +` + +const Key = styled.th` + color: ${p => p.theme.color.slateGray}; + flex: initial; + display: inline-block; + padding: 1px 5px 5px 0; + width: max-content; + line-height: 0.5em; + font-weight: normal; +` + +const Value = styled.td` + color: ${p => p.theme.color.gunMetal}; + padding: 1px 5px 5px 5px; + line-height: normal; + text-overflow: ellipsis; + overflow: hidden !important; + white-space: nowrap; + height: 19px; + max-width: 100px; + font-weight: 500; +` + +const NoValue = styled.td` + padding: 1px 5px 5px 5px; + line-height: normal; + color: ${p => p.theme.color.slateGray}; + font-weight: 300; + line-height: normal; +` diff --git a/frontend/src/features/VesselSidebar/common/TwoColumnKeyValueTable.tsx b/frontend/src/features/VesselSidebar/common/FlatTwoColumnKeyValue.tsx similarity index 90% rename from frontend/src/features/VesselSidebar/common/TwoColumnKeyValueTable.tsx rename to frontend/src/features/VesselSidebar/common/FlatTwoColumnKeyValue.tsx index e6a993300a..efaac82a03 100644 --- a/frontend/src/features/VesselSidebar/common/TwoColumnKeyValueTable.tsx +++ b/frontend/src/features/VesselSidebar/common/FlatTwoColumnKeyValue.tsx @@ -1,4 +1,3 @@ -import { Header } from '@features/VesselSidebar/common_styles/common.style' import { range } from 'lodash' import styled from 'styled-components' @@ -10,13 +9,11 @@ type KeyValue = { type KeyValueTableProps = { className?: string | undefined firstColumn: Array - header?: string | undefined secondColumn: Array } -export function TwoColumnKeyValueTable({ className, firstColumn, header, secondColumn }: KeyValueTableProps) { +export function FlatTwoColumnKeyValue({ className, firstColumn, secondColumn }: KeyValueTableProps) { return ( - {!!header &&
{header}
} {firstColumn.map(({ key, value }) => ( From ba4ee42644b17e9a33f23fa3d652d3c3f05d10c8 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Tue, 4 Jun 2024 10:49:22 +0200 Subject: [PATCH 8/9] Refactor software enum --- .../domain/entities/logbook/LogbookMessage.kt | 10 +++---- .../entities/logbook/LogbookSoftware.kt | 28 +++++++++++++++++++ .../domain/use_cases/vessel/GetVessel.kt | 2 +- .../entities/logbook/LogbookSoftwareUTests.kt | 4 +-- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookMessage.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookMessage.kt index 040f05ac04..f82dede799 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookMessage.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookMessage.kt @@ -122,7 +122,7 @@ data class LogbookMessage( enrichGearPortAndSpecyNames(allGears, allPorts, allSpecies) } - enrichAknowledgeCorrectionAndDeletion(contextMessages) + enrichAcknowledgeCorrectionAndDeletion(contextMessages) } fun enrichGearPortAndSpecyNames( @@ -175,7 +175,7 @@ data class LogbookMessage( private fun enrichAcnkowledge(relatedLogbookMessages: List) { if (this.transmissionFormat == LogbookTransmissionFormat.FLUX || - software !== null && software.contains(LogbookSoftware.VISIOCAPTURE.software) + LogbookSoftware.isVisioCapture(software) ) { this.setAcknowledgeAsSuccessful() @@ -213,7 +213,7 @@ data class LogbookMessage( } } - private fun enrichAknowledgeCorrectionAndDeletion(contextLogbookMessages: List) { + private fun enrichAcknowledgeCorrectionAndDeletion(contextLogbookMessages: List) { val referenceLogbookMessage = findReferencedLogbookMessage(contextLogbookMessages) val relatedLogbookMessages = filterRelatedLogbookMessages(contextLogbookMessages) @@ -234,7 +234,7 @@ data class LogbookMessage( } (transmissionFormat == LogbookTransmissionFormat.FLUX), - (software !== null && software.contains(LogbookSoftware.VISIOCAPTURE.software)), + (LogbookSoftware.isVisioCapture(software)), -> { setAcknowledgeAsSuccessful() } @@ -250,7 +250,7 @@ data class LogbookMessage( } private fun enrichIsSentByFailoverSoftware() { - if (software !== null && software.contains(LogbookSoftware.E_SACAPT.software)) { + if (LogbookSoftware.isESacapt(software)) { isSentByFailoverSoftware = true } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt index 8cceeecd2c..601509e4ea 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftware.kt @@ -1,12 +1,40 @@ package fr.gouv.cnsp.monitorfish.domain.entities.logbook enum class LogbookSoftware(val software: String) { + /** + * Examples: + * - JP/05883989/VISIOCaptures V1.7.6 + * - FP/01641449/VISIOCaptures 2.1.6 + * - JP/07210344/VISIOCaptures 2.1.6 + */ VISIOCAPTURE("VISIOCaptures"), + + /** + * Examples: + * - e-Sacapt Secours ERSV3 V 1.3.1 + */ E_SACAPT("e-Sacapt"), ; companion object { + /** + * True for: + * - VisioCaptures in real time (JP and JT) + * - VisioCaptures in sheets (FP) + */ fun isVisioCapture(software: String?): Boolean { + val isVisioCapture = software?.contains(VISIOCAPTURE.software) + + return isVisioCapture ?: false + } + + fun isESacapt(software: String?): Boolean { + val isESacapt = software?.contains(E_SACAPT.software) + + return isESacapt ?: false + } + + fun isVisioCaptureInRealTime(software: String?): Boolean { val isVisioCapture = software?.let { it.startsWith("FT") || it.startsWith("JT") } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt index 7ecc15d204..e5b5d4130d 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/vessel/GetVessel.kt @@ -80,6 +80,6 @@ class GetVessel( it, ) - return@let LogbookSoftware.isVisioCapture(software) + return@let LogbookSoftware.isVisioCaptureInRealTime(software) } ?: false } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt index 338e74c920..94ae202536 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/logbook/LogbookSoftwareUTests.kt @@ -7,7 +7,7 @@ class LogbookSoftwareUTests { @Test fun `isVisioCapture Should return False if the software is not under VisioCaptures`() { // When - val isVisioCapture = LogbookSoftware.isVisioCapture("e-Sacapt") + val isVisioCapture = LogbookSoftware.isVisioCaptureInRealTime("e-Sacapt") // Then assertThat(isVisioCapture).isFalse() @@ -16,7 +16,7 @@ class LogbookSoftwareUTests { @Test fun `isVisioCapture Should return True if the software is under VisioCaptures`() { // When - val isVisioCapture = LogbookSoftware.isVisioCapture("FT-Visio...") + val isVisioCapture = LogbookSoftware.isVisioCaptureInRealTime("FT-Visio...") // Then assertThat(isVisioCapture).isTrue() From cd383b7c65c7661d4623b37e32393b6dde7e0e8c Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Tue, 4 Jun 2024 11:07:26 +0200 Subject: [PATCH 9/9] Update cypress external acess ers/vms tests --- frontend/cypress/e2e/external_monitorfish.spec.ts | 2 +- frontend/cypress/e2e/nav_monitorfish.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/cypress/e2e/external_monitorfish.spec.ts b/frontend/cypress/e2e/external_monitorfish.spec.ts index 75840c0cb2..317fc71fa7 100644 --- a/frontend/cypress/e2e/external_monitorfish.spec.ts +++ b/frontend/cypress/e2e/external_monitorfish.spec.ts @@ -30,6 +30,7 @@ context('External MonitorFish', () => { cy.get('*[data-cy="vessel-menu-summary"]').should('be.visible') cy.get('*[data-cy="vessel-menu-fishing"]').should('be.visible') cy.get('*[data-cy="vessel-menu-controls"]').should('be.visible') + cy.get('*[data-cy="vessel-menu-ers-vms"]').should('be.visible') // Should not include the modify mission button cy.get('*[data-cy="vessel-menu-controls"]').click() @@ -41,7 +42,6 @@ context('External MonitorFish', () => { cy.get('*[data-cy="vessel-control"]').should('not.contain', 'Ouvrir le contrôle') cy.get('*[data-cy="vessel-menu-reporting"]').should('not.exist') - cy.get('*[data-cy="vessel-menu-ers-vms"]').should('not.exist') // Vessel list cy.get('*[data-cy="vessel-list"]').should('exist') diff --git a/frontend/cypress/e2e/nav_monitorfish.spec.ts b/frontend/cypress/e2e/nav_monitorfish.spec.ts index 65d8f9ec36..e8e9dc4021 100644 --- a/frontend/cypress/e2e/nav_monitorfish.spec.ts +++ b/frontend/cypress/e2e/nav_monitorfish.spec.ts @@ -21,6 +21,7 @@ context('Light MonitorFish', () => { cy.get('*[data-cy="vessel-menu-summary"]').should('be.visible') cy.get('*[data-cy="vessel-menu-fishing"]').should('be.visible') cy.get('*[data-cy="vessel-menu-controls"]').should('be.visible') + cy.get('*[data-cy="vessel-menu-ers-vms"]').should('be.visible') // Should not include the modify mission button cy.get('*[data-cy="vessel-menu-controls"]').click() @@ -32,7 +33,6 @@ context('Light MonitorFish', () => { cy.get('*[data-cy="vessel-control"]').should('not.contain', 'Ouvrir le contrôle') cy.get('*[data-cy="vessel-menu-reporting"]').should('not.exist') - cy.get('*[data-cy="vessel-menu-ers-vms"]').should('not.exist') // Vessel list cy.get('*[data-cy="vessel-list"]').should('exist')