ApercuTache.kt 54 lignes · 2339 octets
package fr.ebii.card2vcf.planning

import fr.ebii.card2vcf.data.TacheEntity
import fr.ebii.card2vcf.sync.SousTacheDto
import fr.ebii.card2vcf.sync.syncJson
import java.time.LocalDate

/**
 * Ce qu'une carte kanban affiche d'une tâche planifiée. Extrait du Compose pour
 * être testable, sur le modèle de `KanbanFilter`.
 *
 * Toutes les entrées viennent du serveur et peuvent être malformées après une
 * synchronisation partielle : chaque fonction rend `null` plutôt que de lever.
 */
object ApercuTache {
    /** « 09/01 → 13/01 » selon le rythme du projet ; `null` si la tâche n'est pas datée. */
    fun periode(tache: TacheEntity, joursOuvres: Int): String? {
        val debut = dateOuNull(tache.debut) ?: return null
        return CalendrierOuvre(joursOuvres).periodeAffichee(debut, tache.dureeJours ?: 1)
    }

    /** `(faites, total)` des sous-tâches ; `null` s'il n'y en a aucune. */
    fun avancement(tache: TacheEntity): Pair<Int, Int>? {
        val toutes = etats(tache)
        if (toutes.isEmpty()) return null
        return toutes.count { it } to toutes.size
    }

    /** La tâche finit-elle après l'échéance du projet ? */
    fun enRetard(tache: TacheEntity, joursOuvres: Int, echeance: String?): Boolean {
        val cible = dateOuNull(echeance) ?: return false
        val debut = dateOuNull(tache.debut) ?: return false
        return CalendrierOuvre(joursOuvres).fin(debut, tache.dureeJours ?: 1).isAfter(cible)
    }

    /** Sous-tâches décodées, dans l'ordre ; liste vide si absentes ou illisibles. */
    fun sousTaches(tache: TacheEntity?): List<SousTacheDto> = runCatching {
        syncJson.decodeFromString<List<SousTacheDto>>(tache?.sousTachesJson ?: "[]")
    }.getOrDefault(emptyList())

    /** Teinte de l'étiquette, identique à celle du web. */
    fun couleur(tache: TacheEntity): String? = CouleurEtiquette.pour(tache.etiquette)

    /**
     * États `fait` des sous-tâches ; liste vide si illisible.
     *
     * Décodé par kotlinx-serialization, pas par `org.json` : ce dernier n'est
     * qu'un stub dans les tests unitaires JVM.
     */
    private fun etats(tache: TacheEntity): List<Boolean> = sousTaches(tache).map { it.fait }

    private fun dateOuNull(iso: String?): LocalDate? =
        iso?.let { runCatching { LocalDate.parse(it) }.getOrNull() }
}