diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PriorNotificationPdfDocumentRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PriorNotificationPdfDocumentRepository.kt index 5166cf0b1a..307ff1656c 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PriorNotificationPdfDocumentRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/PriorNotificationPdfDocumentRepository.kt @@ -4,4 +4,5 @@ import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PdfDocument interface PriorNotificationPdfDocumentRepository { fun findByReportId(reportId: String): PdfDocument + fun deleteByReportId(reportId: String) } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt index e428a27cd5..fdc9d5e446 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotification.kt @@ -7,10 +7,9 @@ import fr.gouv.cnsp.monitorfish.domain.entities.logbook.messages.PNO import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.ManualPriorNotificationComputedValues import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotification import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationType -import fr.gouv.cnsp.monitorfish.domain.repositories.GearRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.ManualPriorNotificationRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.PortRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.* +import org.slf4j.Logger +import org.slf4j.LoggerFactory import java.time.ZonedDateTime @UseCase @@ -20,8 +19,11 @@ class CreateOrUpdateManualPriorNotification( private val portRepository: PortRepository, private val vesselRepository: VesselRepository, private val computeManualPriorNotification: ComputeManualPriorNotification, + private val priorNotificationPdfDocumentRepository: PriorNotificationPdfDocumentRepository, private val getPriorNotification: GetPriorNotification, ) { + private val logger: Logger = LoggerFactory.getLogger(CreateOrUpdateManualPriorNotification::class.java) + fun execute( hasPortEntranceAuthorization: Boolean, hasPortLandingAuthorization: Boolean, @@ -126,6 +128,14 @@ class CreateOrUpdateManualPriorNotification( updatedAt = null, ) + if (reportId !== null) { + try { + priorNotificationPdfDocumentRepository.deleteByReportId(reportId) + } catch (e: Exception) { + logger.warn("Could not delete existing PDF document", e) + } + } + val newOrCurrentReportId = manualPriorNotificationRepository.save(newOrNextPriorNotification) val createdOrUpdatedPriorNotification = getPriorNotification.execute(newOrCurrentReportId, true) @@ -164,10 +174,10 @@ class CreateOrUpdateManualPriorNotification( // so we transform that single FAO area into an FAO area per fishing catch. // This means we don't need to set a global PNO message FAO area here. this.faoZone = null - this.isBeingSent = isInVerificationScope + this.isBeingSent = false this.isInVerificationScope = isInVerificationScope this.isSent = false - this.isVerified = existingPnoValue?.isVerified ?: false + this.isVerified = false this.latitude = null this.longitude = null this.note = note diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationNote.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationNote.kt index da1bb3e251..317b4a7801 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationNote.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationNote.kt @@ -3,16 +3,28 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification import fr.gouv.cnsp.monitorfish.config.UseCase import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotification import fr.gouv.cnsp.monitorfish.domain.repositories.LogbookReportRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.PriorNotificationPdfDocumentRepository +import org.slf4j.Logger +import org.slf4j.LoggerFactory @UseCase class UpdatePriorNotificationNote( private val logbookReportRepository: LogbookReportRepository, + private val priorNotificationPdfDocumentRepository: PriorNotificationPdfDocumentRepository, private val getPriorNotification: GetPriorNotification, ) { + private val logger: Logger = LoggerFactory.getLogger(CreateOrUpdateManualPriorNotification::class.java) + fun execute( note: String?, reportId: String, ): PriorNotification { + try { + priorNotificationPdfDocumentRepository.deleteByReportId(reportId) + } catch (e: Exception) { + logger.warn("Could not delete existing PDF document", e) + } + logbookReportRepository.updatePriorNotificationNote( reportId = reportId, note = note, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PriorNotificationPdfDocumentEntity.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PriorNotificationPdfDocumentEntity.kt index 6a9b909cd2..494e739cf2 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PriorNotificationPdfDocumentEntity.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/entities/PriorNotificationPdfDocumentEntity.kt @@ -14,10 +14,12 @@ data class PriorNotificationPdfDocumentEntity( @Id @Column(name = "report_id") val reportId: String, + @Column(name = "source", columnDefinition = "prior_notification_source") @Enumerated(EnumType.STRING) @JdbcType(PostgreSQLEnumJdbcType::class) val source: PriorNotificationSource, + @Column(name = "generation_datetime_utc") val generationDatetimeUtc: ZonedDateTime, 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 930020212a..2370784d67 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 @@ -385,6 +385,15 @@ class JpaLogbookReportRepository( val pnoMessage = objectMapper.readValue(logbookReportEntity.message, PNO::class.java) pnoMessage.note = note + /** + * The PNO states are re-initialized, + * - the PDF will be generated + * - the PNO will require another verification before sending + */ + pnoMessage.isBeingSent = false + pnoMessage.isVerified = false + pnoMessage.isSent = false + val nextMessage = objectMapper.writeValueAsString(pnoMessage) val updatedEntity = logbookReportEntity.copy(message = nextMessage) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepository.kt index b6eb7e8f5c..8f5d44e27b 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepository.kt @@ -6,6 +6,7 @@ import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageException import fr.gouv.cnsp.monitorfish.domain.repositories.PriorNotificationPdfDocumentRepository import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces.DBPriorNotificationPdfDocumentRepository import org.springframework.dao.EmptyResultDataAccessException +import org.springframework.data.jpa.repository.Modifying import org.springframework.stereotype.Repository @Repository @@ -19,4 +20,13 @@ class JpaPriorNotificationPdfDocumentRepository( throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND) } } + + @Modifying(clearAutomatically = true) + override fun deleteByReportId(reportId: String) { + return try { + dbPriorNotificationPdfDocumentRepository.deleteById(reportId) + } catch (e: EmptyResultDataAccessException) { + throw BackendUsageException(BackendUsageErrorCode.NOT_FOUND) + } + } } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt index ef9de8974b..dd7a259f4a 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/CreateOrUpdateManualPriorNotificationUTests.kt @@ -4,10 +4,7 @@ import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.given import fr.gouv.cnsp.monitorfish.domain.entities.logbook.LogbookMessagePurpose import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.ManualPriorNotificationComputedValues -import fr.gouv.cnsp.monitorfish.domain.repositories.GearRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.ManualPriorNotificationRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.PortRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.* import fr.gouv.cnsp.monitorfish.fakers.PriorNotificationFaker import fr.gouv.cnsp.monitorfish.fakers.VesselFaker import org.assertj.core.api.Assertions.assertThat @@ -37,6 +34,9 @@ class CreateOrUpdateManualPriorNotificationUTests { @MockBean private lateinit var getPriorNotification: GetPriorNotification + @MockBean + private lateinit var priorNotificationPdfDocumentRepository: PriorNotificationPdfDocumentRepository + @Test fun `execute Should update a manual prior notification`() { val fakePriorNotification = PriorNotificationFaker.fakePriorNotification() @@ -62,6 +62,7 @@ class CreateOrUpdateManualPriorNotificationUTests { portRepository, vesselRepository, computeManualPriorNotification, + priorNotificationPdfDocumentRepository, getPriorNotification, ).execute( hasPortEntranceAuthorization = true, diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationNoteUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationNoteUTests.kt new file mode 100644 index 0000000000..1a5c781dfd --- /dev/null +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/prior_notification/UpdatePriorNotificationNoteUTests.kt @@ -0,0 +1,44 @@ +package fr.gouv.cnsp.monitorfish.domain.use_cases.prior_notification + +import com.nhaarman.mockitokotlin2.given +import fr.gouv.cnsp.monitorfish.domain.repositories.LogbookReportRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.PriorNotificationPdfDocumentRepository +import fr.gouv.cnsp.monitorfish.fakers.PriorNotificationFaker +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.test.context.junit.jupiter.SpringExtension + +@ExtendWith(SpringExtension::class) +class UpdatePriorNotificationNoteUTests { + @MockBean + private lateinit var logbookReportRepository: LogbookReportRepository + + @MockBean + private lateinit var getPriorNotification: GetPriorNotification + + @MockBean + private lateinit var priorNotificationPdfDocumentRepository: PriorNotificationPdfDocumentRepository + + @Test + fun `execute Should update a prior notification note`() { + val fakePriorNotification = PriorNotificationFaker.fakePriorNotification() + + // Given + given(getPriorNotification.execute(fakePriorNotification.reportId!!, false)).willReturn(fakePriorNotification) + + // When + val result = UpdatePriorNotificationNote( + logbookReportRepository, + priorNotificationPdfDocumentRepository, + getPriorNotification, + ).execute( + note = null, + reportId = fakePriorNotification.reportId!!, + ) + + // Then + assertThat(result.reportId).isEqualTo(fakePriorNotification.reportId) + } +} 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 a4b922c1e8..1cc5a82e4a 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 @@ -1174,8 +1174,14 @@ class JpaLogbookReportRepositoryITests : AbstractDBTests() { // Then val updatedDatReport = jpaLogbookReportRepository.findById(109) assertThat((updatedDatReport.message as PNO).note).isEqualTo("A wonderful note") + assertThat((updatedDatReport.message as PNO).isBeingSent).isEqualTo(false) + assertThat((updatedDatReport.message as PNO).isVerified).isEqualTo(false) + assertThat((updatedDatReport.message as PNO).isSent).isEqualTo(false) val updatedCorReport = jpaLogbookReportRepository.findById(1109) assertThat((updatedCorReport.message as PNO).note).isEqualTo("A wonderful note") + assertThat((updatedCorReport.message as PNO).isBeingSent).isEqualTo(false) + assertThat((updatedCorReport.message as PNO).isVerified).isEqualTo(false) + assertThat((updatedCorReport.message as PNO).isSent).isEqualTo(false) } companion object { diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepositoryITests.kt index 7d8a5dc381..dbdce51c43 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaPriorNotificationPdfDocumentRepositoryITests.kt @@ -1,7 +1,10 @@ package fr.gouv.cnsp.monitorfish.infrastructure.database.repositories import fr.gouv.cnsp.monitorfish.domain.entities.prior_notification.PriorNotificationSource +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageErrorCode +import fr.gouv.cnsp.monitorfish.domain.exceptions.BackendUsageException import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchThrowable import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.transaction.annotation.Transactional @@ -24,4 +27,24 @@ class JpaPriorNotificationPdfDocumentRepositoryITests : AbstractDBTests() { assertThat(pdfDocument.generationDatetimeUtc).isEqualTo(ZonedDateTime.parse("2024-07-03T14:45:00Z")) assertThat(pdfDocument.pdfDocument).isNotNull() } + + @Test + @Transactional + fun `deleteByReportId Should delete a pdf document`() { + // Given + val existingPdfDocument = jpaPriorNotificationPdfDocumentRepository.findByReportId("FAKE_OPERATION_102") + assertThat(existingPdfDocument.reportId).isEqualTo("FAKE_OPERATION_102") + + // When + jpaPriorNotificationPdfDocumentRepository.deleteByReportId("FAKE_OPERATION_102") + + // Then + val throwable = catchThrowable { + jpaPriorNotificationPdfDocumentRepository.findByReportId("FAKE_OPERATION_102") + } + + // Then + assertThat(throwable).isNotNull() + assertThat((throwable as BackendUsageException).code).isEqualTo(BackendUsageErrorCode.NOT_FOUND) + } } diff --git a/frontend/cypress/e2e/side_window/prior_notification_card/card.spec.ts b/frontend/cypress/e2e/side_window/prior_notification_card/card.spec.ts index a6723d5400..6de8e6b4d1 100644 --- a/frontend/cypress/e2e/side_window/prior_notification_card/card.spec.ts +++ b/frontend/cypress/e2e/side_window/prior_notification_card/card.spec.ts @@ -184,7 +184,7 @@ context('Side Window > Prior Notification Card > Card', () => { }) }) - it('Should update a note', () => { + it('Should update a note and delete the current PDF', () => { // Given openSideWindowPriorNotification(`CALAMARO`) cy.get('*[name="note"]').should('have.value', '') @@ -195,7 +195,11 @@ context('Side Window > Prior Notification Card > Card', () => { cy.get('*[name="note"]').should('have.value', "Un point d'attention.") cy.wait('@updatePriorNotificationNote') - // Then, the note is saved + // Then, the PDF is deleted + cy.clickButton('Télécharger les documents') + cy.get('li[aria-disabled="true"]').contains('Préavis de débarquement (Document non généré)') + + // The note is saved openSideWindowPriorNotification(`CALAMARO`) cy.get('*[name="note"]').should('have.value', "Un point d'attention.") }) diff --git a/frontend/cypress/e2e/side_window/prior_notification_form/form.spec.ts b/frontend/cypress/e2e/side_window/prior_notification_form/form.spec.ts index 739bfc68f8..2c0fc1d0d7 100644 --- a/frontend/cypress/e2e/side_window/prior_notification_form/form.spec.ts +++ b/frontend/cypress/e2e/side_window/prior_notification_form/form.spec.ts @@ -323,6 +323,9 @@ context('Side Window > Prior Notification Form > Form', () => { reportId: createdPriorNotification.reportId }) + cy.clickButton('Télécharger les documents') + cy.get('li[aria-disabled="true"]').contains('Préavis de débarquement (Document non généré)') + // ----------------------------------------------------------------------- // List @@ -429,6 +432,24 @@ context('Side Window > Prior Notification Form > Form', () => { cy.countRequestsByAlias('@computePriorNotification', 1500).should('be.equal', 6) }) + it('Should permit the resending of an updated manual PNO', () => { + // Given + cy.intercept( + 'POST', + '/bff/v1/prior_notifications/00000000-0000-4000-0000-000000000002/verify_and_send?isManuallyCreated=true' + ).as('verifyAndSendPriorNotification') + editSideWindowPriorNotification('DOS FIN', '00000000-0000-4000-0000-000000000002') + cy.get('button').contains('Diffusé') + cy.fill('Saisi par', 'BOB') + + // When + cy.clickButton('Enregistrer') + + // Then + cy.clickButton('Diffuser') + cy.wait('@verifyAndSendPriorNotification') + }) + it('Should verify and send a manual prior notification', () => { // ------------------------------------------------------------------------- // Add @@ -484,7 +505,7 @@ context('Side Window > Prior Notification Form > Form', () => { cy.get('.Element-Tag').contains('Hors diffusion').should('exist') // ----------------------------------------------------------------------- - // Veryify and send + // Verify and send cy.intercept( 'POST', diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 257ee7e8fa..b91d160d2b 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -7,7 +7,7 @@ export type RTKBaseQueryArgs = // Mutation | { body?: AnyObject - method: 'DELETE' | 'POST' | 'PUT' + method: 'GET' | 'DELETE' | 'POST' | 'PUT' /** URL Path (and not full URL). */ url: string } diff --git a/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx b/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx index d1fba2e6b5..5dcdc21a41 100644 --- a/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx +++ b/frontend/src/features/PriorNotification/components/PriorNotificationCard/index.tsx @@ -56,7 +56,6 @@ export function PriorNotificationCard() { state => state.displayedError.sideWindowPriorNotificationCardError ) const [isLoading, setIsLoading] = useState(false) - const isPendingSend = priorNotificationDetail?.state === PriorNotification.State.PENDING_SEND const isSent = [PriorNotification.State.SENT, PriorNotification.State.VERIFIED_AND_SENT].includes( priorNotificationDetail?.state as any ) @@ -180,7 +179,7 @@ export function PriorNotificationCard() { @@ -193,14 +192,13 @@ export function PriorNotificationCard() {