diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt
index c1ea97ddc1..2672f38afd 100644
--- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt
+++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/Utils.kt
@@ -1,5 +1,7 @@
package fr.gouv.cnsp.monitorfish
+import java.time.ZonedDateTime
+
class Utils {
companion object {
/**
@@ -16,5 +18,21 @@ class Utils {
return normalizedLeftString == normalizedRightString
}
+
+ /**
+ * Checks if the ZonedDateTime is between the start and end times.
+ */
+ fun isZonedDateTimeBetween(
+ zonedDateTime: ZonedDateTime,
+ start: ZonedDateTime,
+ end: ZonedDateTime,
+ isInclusive: Boolean = false,
+ ): Boolean {
+ return if (isInclusive) {
+ zonedDateTime >= start && zonedDateTime <= end
+ } else {
+ zonedDateTime > start && zonedDateTime < end
+ }
+ }
}
}
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 d7116aaaa2..59f364bd85 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
@@ -59,10 +59,8 @@ class JpaLogbookReportRepository(
.toSet()
return logbookReportsWithDatCorAndDel
- .filter { report ->
- // Exclude reports that are referenced by other reports or have a DEL operation type
- report.operationType != LogbookOperationType.DEL && report.reportId !in referencedReportIds
- }
+ // Exclude reports that are referenced by other reports or have a DEL operation type
+ .filter { it.operationType != LogbookOperationType.DEL && it.reportId !in referencedReportIds }
.map { report ->
val pno = PriorNotification.fromLogbookMessage(report.toLogbookMessage(objectMapper))
// All messages returned from the SQL query are acknowledged
@@ -70,6 +68,27 @@ class JpaLogbookReportRepository(
return@map pno
}
+ // We filter by predicted arrival date here rather than in the SQL query
+ // because the DAT or COR predicted arrival dates can be far away from each other
+ // which is quite complicated to handle in pure SQL.
+ //
+ // Example: if a DAT that has a `predictedArrivalDatetimeUtc` on DAY 2 at 4pm
+ // is corrected by a COR with a `predictedArrivalDatetimeUtc` on DAY 1 at 4pm,
+ // filtering (in the SQL) between `willArriveAfter` = DAY 2 at 3pm and `willArriveBefore` = DAY 2 at 5pm
+ // would only return the DAT without including the related COR.
+ //
+ // /!\ This is not foolproof:
+ // A difference of more than 48h between DAT and COR `predictedArrivalDatetimeUtc` will still cause issues.
+ .filter {
+ it.logbookMessageAndValue.value.predictedArrivalDatetimeUtc?.let { predictedArrivalDatetimeUtc ->
+ Utils.isZonedDateTimeBetween(
+ predictedArrivalDatetimeUtc,
+ ZonedDateTime.parse(filter.willArriveAfter),
+ ZonedDateTime.parse(filter.willArriveBefore),
+ isInclusive = true,
+ )
+ } == true
+ }
}
@Cacheable(value = ["pno_to_verify"])
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 c2c8cccf9a..b4bbac9124 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
@@ -59,12 +59,6 @@ interface DBLogbookReportRepository :
unaccent(lower(lr.vessel_name)) ILIKE CONCAT('%', unaccent(lower(:searchQuery)), '%') OR
lower(lr.cfr) ILIKE CONCAT('%', lower(:searchQuery), '%')
)
-
- -- Will Arrive After
- AND lr.value->>'predictedArrivalDatetimeUtc' >= :willArriveAfter
-
- -- Will Arrive Before
- AND lr.value->>'predictedArrivalDatetimeUtc' <= :willArriveBefore
),
distinct_cfrs AS (
@@ -203,7 +197,6 @@ interface DBLogbookReportRepository :
),
dels_targeting_searched_pno AS (
-
-- A DEL message has no flag_state, which we need to acknowledge messages of non french vessels.
-- So we use the flag_state of the deleted message.
SELECT del.referenced_report_id, del.operation_number, searched_pno.flag_state
diff --git a/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql b/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql
index d81c7acd6a..646b1f50f1 100644
--- a/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql
+++ b/backend/src/main/resources/db/testdata/V666.2__Insert_dummy_vessels.sql
@@ -84,3 +84,5 @@ INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name,
INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, flag_state, length, under_charter) VALUES (128, 'CFR128', 'MMSI128', 'IRCS128', 'EXTIMM128', 'THE FLOATING KANGAROO', 'AU', 31, false);
INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, flag_state, length, under_charter) VALUES (129, 'CFR129', 'MMSI129', 'IRCS129', 'EXTIMM129', 'BON VENT', 'FR', 34.5, false);
+
+INSERT INTO vessels (id, cfr, mmsi, ircs, external_immatriculation, vessel_name, flag_state, length, under_charter) VALUES (130, 'CFR130', 'MMSI130', 'IRCS130', 'EXTIMM130', 'L''HIPPO CAMPE', 'FR', 19.2, false);
diff --git a/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql b/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql
index 628fd84a1c..f6883f2b24 100644
--- a/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql
+++ b/backend/src/main/resources/db/testdata/V666.5.1__Insert_more_pno_logbook_reports.sql
@@ -93,6 +93,14 @@ INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_O
INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_121_RET', 'Message FLUX xml');
+INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122', 'Message FLUX xml');
+
+INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122_RET', 'Message FLUX xml');
+
+INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122_COR', 'Message FLUX xml');
+
+INSERT INTO logbook_raw_messages (operation_number, xml_message) VALUES ('FAKE_OPERATION_122_COR_RET', 'Message FLUX xml');
+
INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, vessel_name, trip_gears, trip_segments, value) VALUES (101, 'FAKE_OPERATION_101', NULL, 'FAK000999999', true, 'FRA', NOW() AT TIME ZONE 'UTC' - INTERVAL '15 minutes', 'PNO', NOW() AT TIME ZONE 'UTC' - INTERVAL '15 minutes', 'FAKE_OPERATION_101', 'DAT', NOW() AT TIME ZONE 'UTC' - INTERVAL '15 minutes', 'JT/VISIOCaptures V1.4.7', 'ERS', 'PHENOMENE', '[{"gear":"TBN","mesh":100,"dimensions":"250;180"},{"gear":"OTT","mesh":120.5,"dimensions":"250;280"}]', '[{"segment":"SWW04","segmentName":"Chaluts pélagiques"},{"segment":"SWW06","segmentName":"Sennes"}]', '{"riskFactor":2.1,"catchOnboard":[{"weight":25,"nbFish":null,"species":"COD","faoZone":"27.8.a","effortZone":"C","economicZone":"FRA","statisticalRectangle":"23E6"}],"isBeingSent":false,"isInVerificationScope":false,"isSent":false,"isVerified":false,"pnoTypes":[{"pnoTypeName":"Préavis type A","minimumNotificationPeriod":4,"hasDesignatedPorts":false},{"pnoTypeName":"Préavis type B","minimumNotificationPeriod":8,"hasDesignatedPorts":true}],"port":"FRSML","predictedArrivalDatetimeUtc":null,"predictedLandingDatetimeUtc":null,"purpose":"LAN","tripStartDate":null}');
UPDATE logbook_reports SET value = JSONB_SET(value, '{predictedArrivalDatetimeUtc}', TO_JSONB(TO_CHAR(NOW() AT TIME ZONE 'UTC' + INTERVAL '3 hours', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')), true) WHERE id = 101;
UPDATE logbook_reports SET value = JSONB_SET(value, '{predictedLandingDatetimeUtc}', TO_JSONB(TO_CHAR(NOW() AT TIME ZONE 'UTC' + INTERVAL '3.5 hours', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')), true) WHERE id = 101;
@@ -241,3 +249,11 @@ INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_da
INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, trip_gears, trip_segments, vessel_name, value) VALUES (121, 'FAKE_OPERATION_121', NULL, 'ABC000180832', true, 'FRA', NOW() - INTERVAL '15 minutes', 'DEP', NOW() - INTERVAL '15 minutes', 'FAKE_OPERATION_121', 'DAT', NOW() - INTERVAL '15 minutes', 'TurboCatch (3.7-1)', 'ERS', NULL, NULL, 'MARIAGE ÎLE HASARD', '{"gearOnboard":[{"gear":"GTR","mesh":100}],"departurePort":"AEJAZ","anticipatedActivity":"FSH","tripStartDate":"NOW() - INTERVAL ''15 minutes''","departureDatetimeUtc":"NOW() - INTERVAL ''15 minutes''"}');
INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_datetime_utc, operation_datetime_utc, operation_number, operation_type, transmission_format, value) VALUES (1120, NULL, 'FAKE_OPERATION_121', NOW() - INTERVAL '14 minutes', NOW() - INTERVAL '14 minutes', 'FAKE_OPERATION_121_RET', 'RET', 'ERS', '{"returnStatus":"000"}');
+
+INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, trip_gears, trip_segments, vessel_name, value) VALUES (122, 'FAKE_OPERATION_122', NULL, 'CFR130', true, 'FRA', '2024-09-01 13:00:00', 'PNO', '2024-09-01 13:00:00', 'FAKE_OPERATION_122', 'DAT', '2024-09-01 13:00:00', 'TurboCatch (3.7-1)', 'ERS', NULL, NULL, 'L''HIPPO CAMPE', '{"riskFactor":2.9,"catchOnboard":[{"weight":150,"nbFish":null,"species":"ANF","faoZone":"27.8.a","effortZone":"C","economicZone":"FRA","statisticalRectangle":"23E6"}],"pnoTypes":[{"pnoTypeName":"Préavis type Z","minimumNotificationPeriod":4,"hasDesignatedPorts":false}],"port":"BROIA","predictedArrivalDatetimeUtc":"2024-09-02T21:00:00Z","predictedLandingDatetimeUtc":"2024-09-02T21:30:00Z","purpose":"LAN","tripStartDate":"2024-09-01T06:00:00Z"}');
+
+INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_datetime_utc, operation_datetime_utc, operation_number, operation_type, transmission_format, value) VALUES (1122, NULL, 'FAKE_OPERATION_122', '2024-09-01 13:05:00', '2024-09-01 13:05:00', 'FAKE_OPERATION_122_RET', 'RET', 'ERS', '{"returnStatus":"000"}');
+
+INSERT INTO logbook_reports (id, report_id, referenced_report_id, cfr, enriched, flag_state, integration_datetime_utc, log_type, operation_datetime_utc, operation_number, operation_type, report_datetime_utc, software, transmission_format, trip_gears, trip_segments, vessel_name, value) VALUES (2122, 'FAKE_OPERATION_122_COR', 'FAKE_OPERATION_122', 'CFR130', true, 'FRA', '2024-09-01 13:20:00', 'PNO', '2024-09-01 13:20:00', 'FAKE_OPERATION_122', 'COR', '2024-09-01 13:20:00', 'TurboCatch (3.7-1)', 'ERS', NULL, NULL, 'L''HIPPO CAMPE', '{"riskFactor":2.9,"catchOnboard":[{"weight":150,"nbFish":null,"species":"ANF","faoZone":"27.8.a","effortZone":"C","economicZone":"FRA","statisticalRectangle":"23E6"}],"pnoTypes":[{"pnoTypeName":"Préavis type Z","minimumNotificationPeriod":4,"hasDesignatedPorts":false}],"port":"BROIA","predictedArrivalDatetimeUtc":"2024-09-01T15:00:00Z","predictedLandingDatetimeUtc":"2024-09-01T15:30:00Z","purpose":"LAN","tripStartDate":"2024-09-01T06:00:00Z"}');
+
+INSERT INTO logbook_reports (id, report_id, referenced_report_id, integration_datetime_utc, operation_datetime_utc, operation_number, operation_type, transmission_format, value) VALUES (3122, NULL, 'FAKE_OPERATION_122_COR', '2024-09-01 13:25:00', '2024-09-01 13:25:00', 'FAKE_OPERATION_122_COR_RET', 'RET', 'ERS', '{"returnStatus":"000"}');
diff --git a/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc b/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc
index 221866afd4..480565df23 100644
--- a/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc
+++ b/backend/src/main/resources/db/testdata/json/V666.2__Insert_dummy_vessels.jsonc
@@ -825,6 +825,19 @@
"flag_state": "FR",
"length": 34.5,
"under_charter": false
+ },
+
+ // - Vessel: L'HIPPO CAMPE
+ {
+ "id": 130,
+ "cfr": "CFR130",
+ "mmsi": "MMSI130",
+ "ircs": "IRCS130",
+ "external_immatriculation": "EXTIMM130",
+ "vessel_name": "L'HIPPO CAMPE",
+ "flag_state": "FR",
+ "length": 19.2,
+ "under_charter": false
}
]
}
diff --git a/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc b/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc
index 8f7d2797fe..9e6889b8a6 100644
--- a/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc
+++ b/backend/src/main/resources/db/testdata/json/V666.5.1__Insert_more_pno_logbook_reports.jsonc
@@ -47,7 +47,11 @@
{ "operation_number": "FAKE_OPERATION_120", "xml_message": "Message FLUX xml" },
{ "operation_number": "FAKE_OPERATION_121", "xml_message": "Message FLUX xml" },
{ "operation_number": "FAKE_OPERATION_120_RET", "xml_message": "Message FLUX xml" },
- { "operation_number": "FAKE_OPERATION_121_RET", "xml_message": "Message FLUX xml" }
+ { "operation_number": "FAKE_OPERATION_121_RET", "xml_message": "Message FLUX xml" },
+ { "operation_number": "FAKE_OPERATION_122", "xml_message": "Message FLUX xml" },
+ { "operation_number": "FAKE_OPERATION_122_RET", "xml_message": "Message FLUX xml" },
+ { "operation_number": "FAKE_OPERATION_122_COR", "xml_message": "Message FLUX xml" },
+ { "operation_number": "FAKE_OPERATION_122_COR_RET", "xml_message": "Message FLUX xml" }
]
},
{
@@ -1528,7 +1532,7 @@
"trip_segments": null,
"vessel_name": "MARIAGE ÎLE HASARD",
"value:jsonb": {
- "gearOnboard": [{"gear": "GTR", "mesh": 100.0}],
+ "gearOnboard": [{ "gear": "GTR", "mesh": 100.0 }],
"departurePort": "AEJAZ",
"anticipatedActivity": "FSH",
"tripStartDate": "NOW() - INTERVAL '15 minutes'",
@@ -1547,6 +1551,126 @@
"value:jsonb": {
"returnStatus": "000"
}
+ },
+
+ // - Vessel: L'HIPPO CAMPE
+ // - Flag state: FR
+ // - DAT with a predicted arrival date 30h after the COR predicted arrival
+ {
+ "id": 122,
+ "report_id": "FAKE_OPERATION_122",
+ "referenced_report_id": null,
+ "cfr": "CFR130",
+ "enriched": true,
+ "flag_state": "FRA",
+ "integration_datetime_utc": "2024-09-01 13:00:00",
+ "log_type": "PNO",
+ "operation_datetime_utc": "2024-09-01 13:00:00",
+ "operation_number": "FAKE_OPERATION_122",
+ "operation_type": "DAT",
+ "report_datetime_utc": "2024-09-01 13:00:00",
+ "software": "TurboCatch (3.7-1)",
+ "transmission_format": "ERS",
+ "trip_gears": null,
+ "trip_segments": null,
+ "vessel_name": "L'HIPPO CAMPE",
+ "value:jsonb": {
+ "riskFactor": 2.9,
+ "catchOnboard": [
+ {
+ "weight": 150.0,
+ "nbFish": null,
+ "species": "ANF",
+ "faoZone": "27.8.a",
+ "effortZone": "C",
+ "economicZone": "FRA",
+ "statisticalRectangle": "23E6"
+ }
+ ],
+ "pnoTypes": [
+ {
+ "pnoTypeName": "Préavis type Z",
+ "minimumNotificationPeriod": 4.0,
+ "hasDesignatedPorts": false
+ }
+ ],
+ "port": "BROIA",
+ "predictedArrivalDatetimeUtc": "2024-09-02T21:00:00Z",
+ "predictedLandingDatetimeUtc": "2024-09-02T21:30:00Z",
+ "purpose": "LAN",
+ "tripStartDate": "2024-09-01T06:00:00Z"
+ }
+ },
+ {
+ "id": 1122,
+ "report_id": null,
+ "referenced_report_id": "FAKE_OPERATION_122",
+ "integration_datetime_utc": "2024-09-01 13:05:00",
+ "operation_datetime_utc": "2024-09-01 13:05:00",
+ "operation_number": "FAKE_OPERATION_122_RET",
+ "operation_type": "RET",
+ "transmission_format": "ERS",
+ "value:jsonb": {
+ "returnStatus": "000"
+ }
+ },
+ {
+ "id": 2122,
+ "report_id": "FAKE_OPERATION_122_COR",
+ "referenced_report_id": "FAKE_OPERATION_122",
+ "cfr": "CFR130",
+ "enriched": true,
+ "flag_state": "FRA",
+ "integration_datetime_utc": "2024-09-01 13:20:00",
+ "log_type": "PNO",
+ "operation_datetime_utc": "2024-09-01 13:20:00",
+ "operation_number": "FAKE_OPERATION_122",
+ "operation_type": "COR",
+ "report_datetime_utc": "2024-09-01 13:20:00",
+ "software": "TurboCatch (3.7-1)",
+ "transmission_format": "ERS",
+ "trip_gears": null,
+ "trip_segments": null,
+ "vessel_name": "L'HIPPO CAMPE",
+ "value:jsonb": {
+ "riskFactor": 2.9,
+ "catchOnboard": [
+ {
+ "weight": 150.0,
+ "nbFish": null,
+ "species": "ANF",
+ "faoZone": "27.8.a",
+ "effortZone": "C",
+ "economicZone": "FRA",
+ "statisticalRectangle": "23E6"
+ }
+ ],
+ "pnoTypes": [
+ {
+ "pnoTypeName": "Préavis type Z",
+ "minimumNotificationPeriod": 4.0,
+ "hasDesignatedPorts": false
+ }
+ ],
+ "port": "BROIA",
+ "predictedArrivalDatetimeUtc": "2024-09-01T15:00:00Z",
+ "predictedLandingDatetimeUtc": "2024-09-01T15:30:00Z",
+ "purpose": "LAN",
+ "tripStartDate": "2024-09-01T06:00:00Z"
+ }
+ },
+ {
+ "id": 3122,
+ "report_id": null,
+ "referenced_report_id": "FAKE_OPERATION_122_COR",
+ "integration_datetime_utc": "2024-09-01 13:25:00",
+ "operation_datetime_utc": "2024-09-01 13:25:00",
+ "operation_number": "FAKE_OPERATION_122_COR_RET",
+ "operation_type": "RET",
+ "transmission_format": "ERS",
+ "value:jsonb": {
+ "returnStatus": "000"
+ }
}
]
}
diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt
index ef92df529e..b51ff7b11b 100644
--- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt
+++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/UtilsUTests.kt
@@ -2,8 +2,12 @@ package fr.gouv.cnsp.monitorfish
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
+import java.time.ZonedDateTime
class UtilsUTests {
+ private val defaultStart = ZonedDateTime.parse("2024-01-01T12:00:00Z")
+ private val defaultEnd = ZonedDateTime.parse("2024-01-01T14:00:00Z")
+
@Test
fun `areStringsEqual should always return true when both strings are null or emptyish`() {
assertThat(Utils.areStringsEqual("", "")).isTrue()
@@ -24,4 +28,178 @@ class UtilsUTests {
assertThat(Utils.areStringsEqual("", "test")).isFalse()
assertThat(Utils.areStringsEqual(null, "test")).isFalse()
}
+
+ @Test
+ fun `isBetween Should return TRUE when CustomDateTime date is between start and end boundaries`() {
+ // Given
+ val start = ZonedDateTime.parse("2024-01-01T12:00:00Z")
+ val end = ZonedDateTime.parse("2024-01-01T14:00:00Z")
+
+ // When
+ val result = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:00:00Z"), start, end)
+
+ // Then
+ assertThat(result).isTrue
+ }
+
+ @Test
+ fun `isBetween Should return FALSE when CustomDateTime date is outside the boundaries`() {
+ // When
+ val firstRresult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T11:00:00Z"), defaultStart, defaultEnd)
+
+ // Then
+ assertThat(firstRresult).isFalse
+
+ // When
+ val secondRresult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T15:00:00Z"), defaultStart, defaultEnd)
+
+ // Then
+ assertThat(secondRresult).isFalse
+ }
+
+ @Test
+ fun `isBetween Should return FALSE when CustomDateTime date is on the boundary and isInclusive is FALSE`() {
+ // Given
+ val isInclusive = false
+
+ // When
+ val firstResult =
+ Utils.isZonedDateTimeBetween(
+ ZonedDateTime.parse("2024-01-01T12:00:00Z"),
+ defaultStart,
+ defaultEnd,
+ isInclusive,
+ )
+
+ // Then
+ assertThat(firstResult).isFalse
+
+ // When
+ val secondResult =
+ Utils.isZonedDateTimeBetween(
+ ZonedDateTime.parse("2024-01-01T14:00:00Z"),
+ defaultStart,
+ defaultEnd,
+ isInclusive,
+ )
+
+ // Then
+ assertThat(secondResult).isFalse
+ }
+
+ @Test
+ fun `isBetween Should return TRUE when CustomDateTime date is on the boundary and isInclusive is TRUE`() {
+ // Given
+ val isInclusive = true
+
+ // When
+ val firstResult =
+ Utils.isZonedDateTimeBetween(
+ ZonedDateTime.parse("2024-01-01T12:00:00Z"),
+ defaultStart,
+ defaultEnd,
+ isInclusive,
+ )
+
+ // Then
+ assertThat(firstResult).isTrue
+
+ // When
+ val secondResult =
+ Utils.isZonedDateTimeBetween(
+ ZonedDateTime.parse("2024-01-01T14:00:00Z"),
+ defaultStart,
+ defaultEnd,
+ isInclusive,
+ )
+
+ // Then
+ assertThat(secondResult).isTrue
+ }
+
+ @Test
+ fun `isBetween Should handle inclusive and exclusive boundaries as expected when boundaries are equal`() {
+ // Given
+ val start = ZonedDateTime.parse("2024-01-01T12:00:00Z")
+ val end = ZonedDateTime.parse("2024-01-01T12:00:00Z")
+ var isInclusive = false
+
+ // When
+ val firstResult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T12:00:00Z"), start, end, isInclusive)
+
+ // Then
+ assertThat(firstResult).isFalse
+
+ // Given
+ isInclusive = true
+
+ // When
+ val secondResult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T12:00:00Z"), start, end, isInclusive)
+
+ // Then
+ assertThat(secondResult).isTrue
+ }
+
+ @Test
+ fun `isBetween Should handle different time zones correctly`() {
+ // Given
+ val start = ZonedDateTime.parse("2024-01-01T12:00:00+02:00")
+ val end = ZonedDateTime.parse("2024-01-01T14:00:00+02:00")
+
+ // When
+ val firstResult = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:00:00+02:00"), start, end)
+
+ // Then
+ assertThat(firstResult).isTrue
+
+ // When
+ val secondResult = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T15:00:00+02:00"), start, end)
+
+ // Then
+ assertThat(secondResult).isFalse
+ }
+
+ @Test
+ fun `isBetween Should handle milliseconds correctly`() {
+ // When
+ val firstResult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T11:59:59.999Z"), defaultStart, defaultEnd)
+
+ // Then
+ assertThat(firstResult).isFalse
+
+ // When
+ val secondResult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T12:00:00.001Z"), defaultStart, defaultEnd)
+
+ // Then
+ assertThat(secondResult).isTrue
+
+ // When
+ val thirdResult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:59:59.999Z"), defaultStart, defaultEnd)
+
+ // Then
+ assertThat(thirdResult).isTrue
+
+ // When
+ val fourthResult =
+ Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T14:00:00.001Z"), defaultStart, defaultEnd)
+
+ // Then
+ assertThat(fourthResult).isFalse
+ }
+
+ @Test
+ fun `isBetween Should return FALSE when start is after end`() {
+ // When
+ val result = Utils.isZonedDateTimeBetween(ZonedDateTime.parse("2024-01-01T13:00:00Z"), defaultEnd, defaultStart)
+
+ // Then
+ assertThat(result).isFalse
+ }
}
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 eda93c2083..f157c0fc19 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
@@ -540,7 +540,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports from ESP & FRA vessels`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports from ESP & FRA vessels`() {
// Given
val filter =
PriorNotificationsFilter(
@@ -566,7 +566,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports with or without reportings`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports with or without reportings`() {
val expectedLogbookReportIdsWithOneOrMoreReportings = listOf(102L, 104L)
// Given
@@ -610,7 +610,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for less or more than 12 meters long vessels`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for less or more than 12 meters long vessels`() {
// Given
val firstFilter =
PriorNotificationsFilter(
@@ -658,7 +658,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for vessels controlled after or before January 1st, 2024`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for vessels controlled after or before January 1st, 2024`() {
// Given
val firstFilter =
PriorNotificationsFilter(
@@ -714,7 +714,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for FRSML & FRVNE ports`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for FRSML & FRVNE ports`() {
// Given
val filter =
PriorNotificationsFilter(
@@ -737,7 +737,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports When using a vessel name`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports When using a vessel name`() {
// Given
val firstFilter =
PriorNotificationsFilter(
@@ -785,7 +785,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports When using a CFR`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports When using a CFR`() {
// Given
val firstFilter =
PriorNotificationsFilter(
@@ -833,7 +833,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for COD & HKE species`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for COD & HKE species`() {
// Given
val filter =
PriorNotificationsFilter(
@@ -857,7 +857,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for Préavis type A & Préavis type C types`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for Préavis type A & Préavis type C types`() {
// Given
val filter =
PriorNotificationsFilter(
@@ -881,7 +881,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for SWW06 & NWW03 segments`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for SWW06 & NWW03 segments`() {
// Given
val filter =
PriorNotificationsFilter(
@@ -909,7 +909,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for OTT & TB gears`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for OTT & TB gears`() {
// Given
val filter =
PriorNotificationsFilter(
@@ -933,7 +933,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return PNO logbook reports for vessels arriving after or before January 1st, 2024`() {
+ fun `findAllAcknowledgedPriorNotifications Should return PNO logbook reports for vessels arriving after or before January 1st, 2024`() {
// Given
val firstFilter =
PriorNotificationsFilter(
@@ -975,7 +975,7 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
@Test
@Transactional
- fun `findAllPriorNotifications Should return the expected PNO logbook reports with multiple filters`() {
+ fun `findAllAcknowledgedPriorNotifications Should return the expected PNO logbook reports with multiple filters`() {
// Given
val filter =
PriorNotificationsFilter(
@@ -1010,9 +1010,45 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() {
).isTrue()
}
+ // Non-regression test
@Test
@Transactional
- fun `findPriorNotificationsToVerify Should return logbook reports PNO to verify`() {
+ fun `findAllAcknowledgedPriorNotifications Should return the expected result with a COR predicted arrival date far away from the DAT one`() {
+ // FAKE_OPERATION_122 `predictedArrivalDatetimeUtc` is 2024-09-02T21:00:00Z
+ // FAKE_OPERATION_122_COR `predictedArrivalDatetimeUtc` is 2024-09-01T15:00:00Z
+
+ // Given
+ val firstFilter =
+ PriorNotificationsFilter(
+ willArriveAfter = "2024-09-01T13:00:00Z",
+ willArriveBefore = "2024-09-01T17:00:00Z",
+ )
+
+ // When
+ val firstResult = jpaLogbookReportRepository.findAllAcknowledgedPriorNotifications(firstFilter)
+
+ // Then
+ assertThat(firstResult).hasSize(1)
+ val firstResultPriorNotification = firstResult.first()
+ assertThat(firstResultPriorNotification.reportId).isEqualTo("FAKE_OPERATION_122_COR")
+
+ // Given
+ val secondFilter =
+ PriorNotificationsFilter(
+ willArriveAfter = "2024-09-02T19:00:00Z",
+ willArriveBefore = "2024-09-02T23:00:00Z",
+ )
+
+ // When
+ val secondResult = jpaLogbookReportRepository.findAllAcknowledgedPriorNotifications(secondFilter)
+
+ // Then
+ assertThat(secondResult).isEmpty()
+ }
+
+ @Test
+ @Transactional
+ fun `findAllPriorNotificationsToVerify Should return logbook reports PNO to verify`() {
// When
val result = jpaLogbookReportRepository.findAllPriorNotificationsToVerify()