Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Infractions] Ajout du champ "Nb de cibles avec cette infraction" #1574

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ data class InfractionEntity(
val infractionType: InfractionTypeEnum,
val formalNotice: FormalNoticeEnum,
val mmsi: String? = null,
val nbTarget: Int = 1,
val toProcess: Boolean,
val controlledPersonIdentity: String? = null,
val vesselName: String? = null,
val vesselType: VesselTypeEnum? = null,
val vesselSize: Number? = null,
val vesselType: VesselTypeEnum? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ class CreateOrUpdateMission(

@Throws(IllegalArgumentException::class)
fun execute(
mission: MissionEntity?,
mission: MissionEntity,
): MissionEntity {
require(mission != null) { "No mission to create or update" }
val normalizedMission = mission.geom?.let { nonNullGeom ->
mission.copy(geom = postgisFunctionRepository.normalizeMultipolygon(nonNullGeom))
} ?: mission
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ data class MissionEnvActionControlInfractionDataInput(
val infractionType: InfractionTypeEnum,
val mmsi: String? = null,
val natinf: List<String>? = listOf(),
val nbTarget: Int,
val observations: String? = null,
val registrationNumber: String? = null,
val relevantCourt: String? = null,
Expand All @@ -35,6 +36,7 @@ data class MissionEnvActionControlInfractionDataInput(
infractionType = infractionType,
mmsi = mmsi,
natinf = natinf,
nbTarget = nbTarget,
observations = observations,
registrationNumber = registrationNumber,
relevantCourt = relevantCourt,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
BEGIN;

-- Décomposer le tableau en éléments individuels
WITH decomposed AS (SELECT id,
jsonb_array_elements(value -> 'infractions') AS infractions,
value - 'infractions' AS value_without_infractions
FROM env_actions),

-- Ajouter la nouvelle clé à chaque élément
updated AS (SELECT id,
infractions || jsonb_build_object('nbTarget', 1) AS updated_infractions,
value_without_infractions
FROM decomposed),

-- Recomposer les objets modifiés en un tableau
recomposed AS (SELECT id,
jsonb_agg(updated_infractions) AS new_infractions,
value_without_infractions
FROM updated
GROUP BY id, value_without_infractions)

-- Mettre à jour la table avec les nouveaux objets
UPDATE env_actions
SET value = value_without_infractions || jsonb_build_object('infractions', new_infractions)
FROM recomposed
WHERE env_actions.id = recomposed.id;

COMMIT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
BEGIN;

-- Décomposer le tableau en éléments individuels
WITH decomposed AS (SELECT id,
jsonb_array_elements(value -> 'infractions') AS obj,
value - 'infractions' AS data_without_items
FROM env_actions),

-- Ajouter la nouvelle clé à chaque élément
updated AS (SELECT id,
obj || jsonb_build_object('nbTarget', 1) AS updated_obj,
data_without_items
FROM decomposed),

-- Recomposer les objets modifiés en un tableau
recomposed AS (SELECT id,
jsonb_agg(updated_obj) AS new_items,
data_without_items
FROM updated
GROUP BY id, data_without_items)

-- Mettre à jour la table avec les nouveaux objets
UPDATE env_actions
SET value = data_without_items || jsonb_build_object('infractions', new_items)
FROM recomposed
WHERE env_actions.id = recomposed.id;

COMMIT;
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

package fr.gouv.cacem.monitorenv.domain.use_cases.missions

import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.argThat
import com.nhaarman.mockitokotlin2.given
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.*
import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionEntity
import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum
import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionTypeEnum
Expand All @@ -27,7 +23,7 @@ import org.locationtech.jts.io.WKTReader
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.test.context.junit.jupiter.SpringExtension
import java.time.ZonedDateTime
import java.util.UUID
import java.util.*

@ExtendWith(SpringExtension::class)
class CreateOrPatchEnvActionsUTests {
Expand Down Expand Up @@ -163,7 +159,6 @@ class CreateOrPatchEnvActionsUTests {
)

// Then
// verify(facadeAreasRepository, times(1)).findFacadeFromGeometry(argThat { this == polygon })
verify(facadeAreasRepository, times(1)).findFacadeFromGeometry(argThat { this == point })
verify(departmentRepository, times(1)).findDepartmentFromGeometry(argThat { this == polygon })
verify(departmentRepository, times(1)).findDepartmentFromGeometry(argThat { this == point })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import fr.gouv.cacem.monitorenv.domain.repositories.IFacadeAreasRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IPostgisFunctionRepository
import fr.gouv.cacem.monitorenv.domain.use_cases.missions.dtos.MissionDTO
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
Expand All @@ -28,32 +27,17 @@ import java.util.*

@ExtendWith(SpringExtension::class)
class CreateOrUpdateMissionUTests {
@MockBean private lateinit var missionRepository: IMissionRepository
@MockBean
private lateinit var missionRepository: IMissionRepository

@MockBean private lateinit var facadeAreasRepository: IFacadeAreasRepository
@MockBean
private lateinit var facadeAreasRepository: IFacadeAreasRepository

@MockBean private lateinit var postgisFunctionRepository: IPostgisFunctionRepository
@MockBean
private lateinit var postgisFunctionRepository: IPostgisFunctionRepository

@MockBean private lateinit var applicationEventPublisher: ApplicationEventPublisher

@Test
fun `execute Should throw an exception when input mission is null`() {
// When
val throwable =
Assertions.catchThrowable {
CreateOrUpdateMission(
missionRepository = missionRepository,
facadeRepository = facadeAreasRepository,
eventPublisher = applicationEventPublisher,
postgisFunctionRepository = postgisFunctionRepository,
)
.execute(null)
}

// Then
assertThat(throwable).isInstanceOf(IllegalArgumentException::class.java)
assertThat(throwable.message).contains("No mission to create or update")
}
@MockBean
private lateinit var applicationEventPublisher: ApplicationEventPublisher

@Test
fun `should return the mission to update with computed facade`() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ context('Side Window > Mission Form > Attach action to reporting', () => {
cy.getDataCy('infraction-form-controlledPersonIdentity').should('have.value', 'Capitaine Crochet')
cy.getDataCy('infraction-form-vesselName').should('have.value', 'Le Bateau 2000')
cy.getDataCy('infraction-form-vessel-size').should('have.value', 13)
cy.getDataCy('infraction-form-nbTarget').should('have.value', 1)
cy.fill("Type d'infraction", 'Avec PV')
cy.fill('Réponse administrative', 'Sanction')
cy.fill('Mise en demeure', 'En attente')
Expand Down Expand Up @@ -238,6 +239,7 @@ context('Side Window > Mission Form > Attach action to reporting', () => {
.contains('Sanction')
cy.get('.Field-MultiRadio').contains('Mise en demeure').get('[aria-checked="true"]').contains('En attente')
cy.get('[name="infraction-natinf"]').should('have.value', 1508)
cy.getDataCy('infraction-form-nbTarget').should('have.value', 1)
}
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ context('Side Window > Mission Form > Mission actions', () => {
expect(duplicatedInfraction.toProcess).equal(false)
expect(duplicatedInfraction.vesselSize).equal(45)
expect(duplicatedInfraction.vesselType).equal('COMMERCIAL')
expect(duplicatedInfraction.nbTarget).equal(1)
expect(duplicatedInfraction.id).not.equal('b8007c8a-5135-4bc3-816f-c69c7b75d807')
})
})
Expand Down Expand Up @@ -499,7 +500,7 @@ context('Side Window > Mission Form > Mission actions', () => {
cy.clickButton('Ajouter des contrôles')
cy.wait(500)

cy.fill('Nb total de contrôles', 1)
cy.fill('Nb total de contrôles', 2)
cy.fill('Type de cible', 'Véhicule')
cy.fill('Type de véhicule', 'Navire')

Expand All @@ -515,6 +516,7 @@ context('Side Window > Mission Form > Mission actions', () => {
cy.fill('Réponse administrative', 'Sanction')
cy.fill('Mise en demeure', 'Oui')
cy.fill('NATINF', ["1508 - Execution d'un travail dissimule"])
cy.fill('Nb de cibles avec cette infraction', 1)

cy.getDataCy('control-open-by').type('ABC')
cy.wait(250)
Expand All @@ -533,6 +535,7 @@ context('Side Window > Mission Form > Mission actions', () => {
expect(requestInfraction.administrativeResponse).equal('SANCTION')
expect(requestInfraction.formalNotice).equal('YES')
expect(requestInfraction.natinf).to.deep.equal(['1508'])
expect(requestInfraction.nbTarget).equal(1)

// check response
const responseInfraction = response?.body.envActions[0].infractions[0]
Expand All @@ -548,6 +551,7 @@ context('Side Window > Mission Form > Mission actions', () => {
expect(requestInfraction.administrativeResponse).equal('SANCTION')
expect(requestInfraction.formalNotice).equal('YES')
expect(requestInfraction.natinf).to.deep.equal(['1508'])
expect(requestInfraction.nbTarget).equal(1)

// clean
cy.wait(250)
Expand Down Expand Up @@ -581,16 +585,16 @@ context('Side Window > Mission Form > Mission actions', () => {
cy.clickButton("Valider l'infraction")

// cases without identification
cy.getDataCy('infraction-0-identification').contains('Personne morale')
cy.getDataCy('infraction-0-identification').contains('1 personne morale')

cy.fill('Type de cible', 'Personne physique')
cy.getDataCy('infraction-0-identification').contains('Personne physique')
cy.getDataCy('infraction-0-identification').contains('1 personne physique')

cy.fill('Type de cible', 'Véhicule')
cy.getDataCy('infraction-0-identification').contains('Véhicule - Type non renseigné')
cy.getDataCy('infraction-0-identification').contains('1 véhicule - Type non renseigné')

cy.fill('Type de véhicule', 'Navire')
cy.getDataCy('infraction-0-identification').contains('Véhicule - Navire')
cy.getDataCy('infraction-0-identification').contains('1 véhicule - Navire')

// cases with identification
// Company
Expand Down Expand Up @@ -643,6 +647,43 @@ context('Side Window > Mission Form > Mission actions', () => {
})
})

it('Should display number of infraction target and target type', () => {
createPendingMission().then(({ body }) => {
const mission = body

cy.intercept('PUT', `/bff/v1/missions/${mission.id}`).as('updateMission')

// Add a control
cy.clickButton('Ajouter')
cy.clickButton('Ajouter des contrôles')
cy.wait(500)

cy.fill('Nb total de contrôles', 10)
cy.fill('Type de cible', 'Personne morale')
cy.clickButton('+ Ajouter un contrôle avec infraction')
// Fill mandatory fields
cy.fill("Type d'infraction", 'Avec PV')
cy.fill('Mise en demeure', 'Oui')
cy.fill('NATINF', ["1508 - Execution d'un travail dissimule"])
cy.fill('Réponse administrative', 'Régularisation')
cy.fill('Nb de cibles avec cette infraction', 2)
cy.clickButton("Valider l'infraction")

// cases without identification
cy.getDataCy('infraction-0-identification').contains('2 personnes morales')

cy.fill('Type de cible', 'Personne physique')
cy.getDataCy('infraction-0-identification').contains('2 personnes physiques')

cy.fill('Type de cible', 'Véhicule')
cy.getDataCy('infraction-0-identification').contains('2 véhicules')

// delete created mission
cy.clickButton('Supprimer la mission')
cy.clickButton('Confirmer la suppression')
})
})

it('Should keep pending action when switching tabs', () => {
createPendingMission().then(() => {
// Add a control
Expand Down
1 change: 1 addition & 0 deletions frontend/src/domain/entities/missions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ export type NewInfraction = {
infractionType?: InfractionTypeEnum
mmsi?: string | null
natinf?: string[]
nbTarget: number
observations?: string | null
registrationNumber?: string | null
relevantCourt?: string | null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { useHasMapInteraction } from '../../../../hooks/useHasMapInteraction'

import type { OverlayTriggerType } from 'rsuite/esm/internals/Overlay/OverlayTrigger'

const PHONE_TOOLTIP_STATE = {
type TooltypeType = Record<
'click' | 'hover',
{ className: 'greenTooltip' | 'blueTooltip'; text: string; trigger: OverlayTriggerType }
>

const PHONE_TOOLTIP_STATE: TooltypeType = {
click: {
className: 'greenTooltip',
text: 'Numéro copié',
Expand All @@ -27,7 +32,7 @@ const PHONE_TOOLTIP_STATE = {
}
}

const MAIL_TOOLTIP_STATE = {
const MAIL_TOOLTIP_STATE: TooltypeType = {
click: {
className: 'greenTooltip',
text: 'Mail copié',
Expand All @@ -39,7 +44,7 @@ const MAIL_TOOLTIP_STATE = {
trigger: 'hover'
}
}
const hoverTooltip = (text, className) => <StyledTooltip className={className}>{text}</StyledTooltip>
const hoverTooltip = (text: string, className: string) => <StyledTooltip className={className}>{text}</StyledTooltip>

type SemaphoreCardProps = {
feature: any
Expand Down Expand Up @@ -107,7 +112,7 @@ export function SemaphoreCard({ feature, isSuperUser, selected = false }: Semaph
onClick={onCopyPhone}
placement="left"
speaker={hoverTooltip(tooltipPhoneState.text, tooltipPhoneState.className)}
trigger={tooltipPhoneState.trigger as OverlayTriggerType}
trigger={tooltipPhoneState.trigger}
>
<span>
<StyledCopyButton
Expand All @@ -128,7 +133,7 @@ export function SemaphoreCard({ feature, isSuperUser, selected = false }: Semaph
onClick={onCopyMail}
placement="left"
speaker={hoverTooltip(tooltipMailState.text, tooltipMailState.className)}
trigger={tooltipMailState.trigger as OverlayTriggerType}
trigger={tooltipMailState.trigger}
>
<span>
<StyledCopyButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import { vesselTypeLabel } from 'domain/entities/vesselType'
import type { CSSProperties } from 'styled-components'

type VesselTypeSelectorProps = {
disabled?: boolean
isLight?: boolean
name: string
style?: CSSProperties
}
export function VesselTypeSelector({ isLight = false, name, style }: VesselTypeSelectorProps) {
export function VesselTypeSelector({ disabled = false, isLight = false, name, style }: VesselTypeSelectorProps) {
const vesselTypeFieldList = getOptionsFromLabelledEnum(vesselTypeLabel)

return (
<FormikSelect
block
cleanable={false}
data-cy="vessel-type-selector"
disabled={disabled}
isLight={isLight}
isUndefinedWhenDisabled
label="Type de navire"
name={name}
options={vesselTypeFieldList}
Expand Down
Loading