ApercuTache.swift 46 lignes · 2030 octets
import Foundation

/// Ce qu'une carte kanban affiche d'une tâche planifiée. Extrait de la vue 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 `nil` plutôt que de lever.
///
/// Port de `planning/ApercuTache.kt`.
enum ApercuTache {
    /// « 09/01 → 13/01 » selon le rythme du projet ; `nil` si la tâche n'est pas datée.
    static func periode(_ tache: TacheEntity, joursOuvres: Int) -> String? {
        guard let debut = dateOuNil(tache.debut) else { return nil }
        return CalendrierOuvre(joursOuvres: joursOuvres)
            .periodeAffichee(debut, tache.dureeJours ?? 1)
    }

    /// `(faites, total)` des sous-tâches ; `nil` s'il n'y en a aucune.
    static func avancement(_ tache: TacheEntity) -> (faites: Int, total: Int)? {
        let toutes = sousTaches(tache)
        if toutes.isEmpty { return nil }
        return (toutes.filter(\.fait).count, toutes.count)
    }

    /// La tâche finit-elle après l'échéance du projet ?
    static func enRetard(_ tache: TacheEntity, joursOuvres: Int, echeance: String?) -> Bool {
        guard let cible = dateOuNil(echeance), let debut = dateOuNil(tache.debut) else { return false }
        return CalendrierOuvre(joursOuvres: joursOuvres).fin(debut, tache.dureeJours ?? 1) > cible
    }

    /// Sous-tâches décodées, dans l'ordre ; liste vide si absentes ou illisibles.
    static func sousTaches(_ tache: TacheEntity?) -> [SousTacheDto] {
        let json = tache?.sousTachesJson ?? "[]"
        return (try? SyncJson.decode([SousTacheDto].self, from: json)) ?? []
    }

    /// Teinte de l'étiquette, identique à celle du web.
    static func couleur(_ tache: TacheEntity) -> String? {
        CouleurEtiquette.pour(tache.etiquette)
    }

    private static func dateOuNil(_ iso: String?) -> DateCivile? {
        guard let iso else { return nil }
        return DateCivile(iso: iso)
    }
}