Skip to content

Commit

Permalink
Merge pull request #92 from MTES-MCT/RPN-63
Browse files Browse the repository at this point in the history
Rapport de patrouille
  • Loading branch information
lwih authored Mar 12, 2024
2 parents 676d2f9 + e693ab4 commit a3af906
Show file tree
Hide file tree
Showing 29 changed files with 1,526 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ variables:
value: postgres:15.6-alpine
description: "Image de la base de données"
PROJECT_VERSION:
value: "1.1.0"
value: "1.2.0"
description: "Version du projet à déployer"
SERVER_ENV_INT:
value: "int-rapportnav-appli01"
Expand Down
2 changes: 1 addition & 1 deletion backend/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

group = "fr.gouv.dgampa"
version = "1.1.0"
version = "1.2.0"
description = "RapportNav"

val kotlinVersion by extra("1.9.21")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ data class MissionEntity(
val missionSource: MissionSourceEnum,
val hasMissionOrder: Boolean,
val isUnderJdp: Boolean,
var actions: List<MissionActionEntity>?,
var actions: List<MissionActionEntity>? = null,
) {
constructor(
envMission: ExtendedEnvMissionEntity,
Expand All @@ -50,9 +50,27 @@ data class MissionEntity(
missionSource = envMission.mission.missionSource,
hasMissionOrder = envMission.mission.hasMissionOrder,
isUnderJdp = envMission.mission.isUnderJdp,
actions = (envMission.actions?.map { MissionActionEntity.EnvAction(it) } ?: listOf()) +
(fishMissionActions?.map { MissionActionEntity.FishAction(it) } ?: listOf()) +
(navMission?.actions?.map { MissionActionEntity.NavAction(it) } ?: listOf())

actions = sortActions(
envMission.actions?.map { MissionActionEntity.EnvAction(it) } ?: emptyList(),
fishMissionActions?.map { MissionActionEntity.FishAction(it) } ?: emptyList(),
navMission?.actions?.map { MissionActionEntity.NavAction(it) } ?: emptyList()
)
)

companion object {
private fun sortActions(
envActions: List<MissionActionEntity.EnvAction>,
fishActions: List<MissionActionEntity.FishAction>,
navActions: List<MissionActionEntity.NavAction>
): List<MissionActionEntity> {
return (envActions + fishActions + navActions).sortedByDescending { action ->
when (action) {
is MissionActionEntity.EnvAction -> action.envAction?.controlAction?.action?.actionStartDateTimeUtc
is MissionActionEntity.FishAction -> action.fishAction.controlAction?.action?.actionDatetimeUtc
is MissionActionEntity.NavAction -> action.navAction.startDateTimeUtc
}
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,13 @@ fun mapStringToActionStatusType(value: String): ActionStatusType {
else -> ActionStatusType.UNKNOWN
}
}

fun mapActionStatusTypeToHumanString(value: ActionStatusType): String {
return when (value) {
ActionStatusType.NAVIGATING -> "Navigation"
ActionStatusType.ANCHORED -> "Mouillage"
ActionStatusType.DOCKED -> "Présence à quai"
ActionStatusType.UNAVAILABLE -> "Indisponibilité"
else -> "Inconnu"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fr.gouv.dgampa.rapportnav.domain.repositories.mission

import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.crew.MissionCrewEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.export.MissionExportEntity
import java.time.LocalDate
import java.time.ZonedDateTime

data class ExportParams(
val service: String?,
val id: String,
val startDateTime: ZonedDateTime?,
val endDateTime: ZonedDateTime?,
val presenceMer: Map<String, Int>,
val presenceQuai: Map<String, Int>,
val indisponibilite: Map<String, Int>,
val nbJoursMer: Int,
val dureeMission: Int,
val patrouilleEnv: Int,
val patrouilleMigrant: Int,
val distanceMilles: Float?,
val goMarine: Float?,
val essence: Float?,
val crew: List<MissionCrewEntity>,
val timeline: Map<LocalDate, List<String>>?
)

interface IRpnExportRepository {
fun exportOdt(params: ExportParams): MissionExportEntity?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fr.gouv.dgampa.rapportnav.domain.use_cases.mission

import fr.gouv.dgampa.rapportnav.config.UseCase
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionEntity
import org.slf4j.LoggerFactory

@UseCase
class GetMissionById(
private val getEnvMissionById: GetEnvMissionById,
private val getFishActionsByMissionId: GetFishActionsByMissionId,
private val getNavMissionById: GetNavMissionById,
) {
private val logger = LoggerFactory.getLogger(GetMissionById::class.java)

fun execute(missionId: Int): MissionEntity? {
val envMission = getEnvMissionById.execute(missionId = missionId) ?: return null
val fishMissionActions = getFishActionsByMissionId.execute(missionId = missionId)
val navMission = getNavMissionById.execute(missionId = missionId)

return MissionEntity(envMission, navMission, fishMissionActions)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action

import fr.gouv.dgampa.rapportnav.config.UseCase
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionActionEntity
import java.time.LocalDate

@UseCase
class GroupActionByDate {
fun execute(actions: List<MissionActionEntity>?): Map<LocalDate, List<MissionActionEntity>>? {
return actions?.groupBy { action ->
when (action) {
is MissionActionEntity.EnvAction -> action.envAction?.controlAction?.action?.actionStartDateTimeUtc?.toLocalDate()
is MissionActionEntity.FishAction -> action.fishAction.controlAction?.action?.actionDatetimeUtc?.toLocalDate()
is MissionActionEntity.NavAction -> action.navAction.startDateTimeUtc.toLocalDate()
else -> null // Handle other types of actions, if any
}
}?.mapNotNull { (date, actions) ->
date?.let { it to actions }
}?.toMap()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package fr.gouv.dgampa.rapportnav.domain.use_cases.mission.export

import fr.gouv.dgampa.rapportnav.config.UseCase
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.crew.MissionCrewEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.export.MissionExportEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.generalInfo.MissionGeneralInfoEntity
import fr.gouv.dgampa.rapportnav.domain.repositories.mission.ExportParams
import fr.gouv.dgampa.rapportnav.domain.repositories.mission.IRpnExportRepository
import fr.gouv.dgampa.rapportnav.domain.repositories.mission.action.INavActionStatusRepository
import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.GetMissionById
import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.crew.GetAgentsCrewByMissionId
import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.generalInfo.GetMissionGeneralInfoByMissionId
import org.slf4j.LoggerFactory

@UseCase
class ExportMission(
private val exportRepository: IRpnExportRepository,
private val getMissionGeneralInfoByMissionId: GetMissionGeneralInfoByMissionId,
private val agentsCrewByMissionId: GetAgentsCrewByMissionId,
private val getMissionById: GetMissionById,
private val navActionStatus: INavActionStatusRepository,
private val mapStatusDurations: MapStatusDurations,
private val formatActionsForTimeline: FormatActionsForTimeline,
) {

private val logger = LoggerFactory.getLogger(ExportMission::class.java)

fun exportOdt(missionId: Int): MissionExportEntity? {
try {
val mission: MissionEntity? = getMissionById.execute(missionId = missionId)
if (mission == null) {
logger.error("[exportOdt] Mission not found for missionId: $missionId")
return null
}

val generalInfo: MissionGeneralInfoEntity? = getMissionGeneralInfoByMissionId.execute(missionId)
val agentsCrew: List<MissionCrewEntity> = agentsCrewByMissionId.execute(missionId = missionId)
val statuses = navActionStatus.findAllByMissionId(missionId = missionId).sortedBy { it.startDateTimeUtc }
.map { it.toActionStatusEntity() }

val durations = mapStatusDurations.execute(mission, statuses)
val missionDuration = (durations["atSeaDurations"]?.get("total") ?: 0) +
(durations["dockingDurations"]?.get("total") ?: 0) +
(durations["unavailabilityDurations"]?.get("total") ?: 0)

val timeline = formatActionsForTimeline.formatTimeline(mission.actions)

val exportParams = ExportParams(
service = mission.openBy,
id = "pam" + mission.id,
startDateTime = mission.startDateTimeUtc,
endDateTime = mission.endDateTimeUtc,
presenceMer = durations["atSeaDurations"].orEmpty(),
presenceQuai = durations["dockingDurations"].orEmpty(),
indisponibilite = durations["unavailabilityDurations"].orEmpty(),
nbJoursMer = 0,
dureeMission = missionDuration,
patrouilleEnv = 0,
patrouilleMigrant = 0,
distanceMilles = generalInfo?.distanceInNauticalMiles,
goMarine = generalInfo?.consumedGOInLiters,
essence = generalInfo?.consumedFuelInLiters,
crew = agentsCrew,
timeline = timeline
)

return exportRepository.exportOdt(exportParams)
} catch (e: Exception) {
logger.error("[exportOdt] error occurred during exportOdt: ${e.message}")
return null
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package fr.gouv.dgampa.rapportnav.domain.use_cases.mission.export

import fr.gouv.dgampa.rapportnav.config.UseCase
import fr.gouv.dgampa.rapportnav.domain.entities.mission.MissionActionEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.env.envActions.EnvActionControlEntity
import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.InfractionType
import fr.gouv.dgampa.rapportnav.domain.entities.mission.fish.fishActions.MissionAction
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.action.*
import fr.gouv.dgampa.rapportnav.domain.entities.mission.nav.status.mapActionStatusTypeToHumanString
import fr.gouv.dgampa.rapportnav.domain.use_cases.mission.action.GroupActionByDate
import java.time.LocalDate
import java.time.ZonedDateTime

@UseCase
class FormatActionsForTimeline(
private val groupActionByDate: GroupActionByDate,
) {

fun formatTimeline(actions: List<MissionActionEntity>?): Map<LocalDate, List<String>>? {

if (actions.isNullOrEmpty()) {
return null
}
// Group actions by date
val groupedActions = groupActionByDate.execute(actions = actions)

// Map each group to list of formatted strings
return groupedActions?.mapValues { (_, actionsOnDate) ->
actionsOnDate.mapNotNull { action ->
formatAction(action)
}
}
}

private fun formatAction(action: MissionActionEntity): String? {
return when (action) {
is MissionActionEntity.EnvAction -> formatEnvAction(action)
is MissionActionEntity.FishAction -> formatFishAction(action)
is MissionActionEntity.NavAction -> formatNavAction(action)
}
}

private fun formatEnvAction(action: MissionActionEntity.EnvAction): String? {
return action.envAction?.controlAction?.action?.let { envControlAction ->
formatEnvControl(envControlAction)
}
}

private fun formatFishAction(action: MissionActionEntity.FishAction): String? {
return action.fishAction.controlAction?.action?.let { fishControlAction ->
formatFishControl(fishControlAction)
}
}

private fun formatNavAction(action: MissionActionEntity.NavAction): String? {
val navAction = action.navAction
return when (navAction.actionType) {
ActionType.NOTE -> formatNavNote(navAction.freeNoteAction)
ActionType.STATUS -> formatNavStatus(navAction.statusAction)
ActionType.CONTROL -> formatNavControl(navAction.controlAction)
else -> null
}
}


fun formatTime(dateTime: ZonedDateTime?): String? {
return dateTime?.toLocalTime()?.toString()?.padStart(5, '0') ?: "N/A"
}


fun formatEnvControl(action: EnvActionControlEntity?): String? {
return action?.let {
val startTime = formatTime(action.actionStartDateTimeUtc)
val endTime = formatTime(action.actionEndDateTimeUtc)
val facade = action.facade?.let { " - $it" } ?: ""
val themes = action.themes?.let { " - ${it.map { theme -> theme.theme }.joinToString(" + ")}" } ?: ""
val amountOfControls = action.actionNumberOfControls?.let { " - $it contrôles" } ?: ""
return "$startTime / $endTime - Contrôle Environnement$facade$themes$amountOfControls"
}
}

fun formatFishControl(action: MissionAction?): String? {
return action?.let {
val startTime = formatTime(action.actionDatetimeUtc)
val coords = "${action.latitude ?: "N/A"}/${action.longitude ?: "N/A"}"
val vesselInfo = "${action.vesselName ?: "N/A"} - ${action.vesselId}"
val seizureAndDiversion = action.seizureAndDiversion?.let { " - retour du navire au port" } ?: ""
val natinfs: String = listOf(
action.gearInfractions.map { it.natinf.toString() },
action.logbookInfractions.map { it.natinf.toString() },
action.speciesInfractions.map { it.natinf.toString() },
action.otherInfractions.map { it.natinf.toString() }
).flatten().distinct().let { list ->
if (list.isEmpty()) {
" - RAS"
} else {
" - NATINF: ${list.joinToString(" + ")}"
}
}
val pvCount = listOf(
action.gearInfractions.map { it.infractionType },
action.logbookInfractions.map { it.infractionType },
action.speciesInfractions.map { it.infractionType },
action.otherInfractions.map { it.infractionType }
).flatten().count { it == InfractionType.WITH_RECORD }
val pv = if (pvCount > 0) "avec PV" else "sans PV"

val species = if (action.speciesOnboard.isNotEmpty()) {
"Espèces contrôlées: " + action.speciesOnboard.joinToString(" - ") { "${it.speciesCode}: ${it.controlledWeight ?: "N/A"}/${it.declaredWeight ?: "N/A"} kg" }
} else {
"Espèces contrôlées: N/A"
}

return "$startTime - Contrôle Pêche - $coords - $vesselInfo - $species - Infractions: $pv$natinfs$seizureAndDiversion"
}
}

fun formatNavNote(action: ActionFreeNoteEntity?): String? {
return action?.let {
val startTime = formatTime(action.startDateTimeUtc)
val observation = action.observations ?: ""
return "$startTime - $observation"
}
}


fun formatNavStatus(action: ActionStatusEntity?): String? {
return action?.let {
val startTime = formatTime(action.startDateTimeUtc)
val status = mapActionStatusTypeToHumanString(action.status)
val observation = action.observations?.let { "- $it" } ?: ""
return "$startTime - $status - début $observation"
}
}

fun formatNavControl(action: ActionControlEntity?): String? {
return action?.let {
val startTime = formatTime(action.startDateTimeUtc)
val endTime = formatTime(action.endDateTimeUtc)
val vesselIdentifier = action.vesselIdentifier?.let { "- $it" } ?: ""
return "$startTime / $endTime - Contrôle administratif $vesselIdentifier"
}
}
}
Loading

0 comments on commit a3af906

Please sign in to comment.