diff --git a/app/calcul.py b/app/calcul.py index 5df373d..2735513 100644 --- a/app/calcul.py +++ b/app/calcul.py @@ -202,8 +202,6 @@ def compute_week(pointages: list[dict], conges: list[dict], heures_jour_min: int left -= give for i, s in enumerate(slots): - if adjs[i] <= 0: - continue # rythme normal suffit pour cette demi-journée, rien à signaler sortie_min = s["debut"] + s["nominal"] + adjs[i] sortie_min = max(sortie_min, matin_fin_min if s["half"] == "matin" else aprem_fin_min) entree = f"{s['debut'] // 60:02d}:{s['debut'] % 60:02d}" diff --git a/app/tests/test_calcul.py b/app/tests/test_calcul.py index 19909d9..207ee29 100644 --- a/app/tests/test_calcul.py +++ b/app/tests/test_calcul.py @@ -128,10 +128,11 @@ def test_compute_week_semaine_totalement_vide_compte_les_10_demi_journees(freeze result = compute_week(pointages, [], HEURES_JOUR_MIN) assert result["slots_restants"] == 10 assert result["reste_min"] == HEURES_JOUR_MIN * 5 - # Rien n'est encore en retard puisque rien n'a encore été renseigné : le rythme - # normal (à répartir librement sur les 10 demi-journées) suffit, pas de cible imposée. + # Pas de retard (rien n'a encore été renseigné) : le rythme normal suffit, extra = 0. + # Mais chaque demi-journée reste propose son horaire visé (matin 09:00→12:00, + # aprem 13:30→18:18 avec le surplus nominal basculé depuis le matin saturé). assert result["extra_min"] == 0 - assert all(j["cible_matin"] is None and j["cible_aprem"] is None for j in result["jours"]) + assert all(j["cible_matin"] == "12:00" and j["cible_aprem"] == "18:18" for j in result["jours"]) def test_compute_week_jour_futur_non_entame_recoit_une_cible_repartie(freeze_today): @@ -150,30 +151,34 @@ def test_compute_week_jour_futur_non_entame_recoit_une_cible_repartie(freeze_tod assert lundi_suivant["cible_matin"] is not None or lundi_suivant["cible_aprem"] is not None -def test_compute_week_jour_futur_a_egalite_pas_de_cible(freeze_today): - """Quand le rythme normal des demi-journées restantes suffit déjà, aucune cible n'est affichée.""" +def test_compute_week_jour_futur_a_egalite_propose_le_rythme_normal(freeze_today): + """Quand le rythme normal des demi-journées restantes suffit (pas de rattrapage), + les horaires visés du matin et de l'après-midi sont quand même proposés.""" dates_completes = ["2026-07-13", "2026-07-14", "2026-07-15", "2026-07-16", "2026-07-17"] pointages = _semaine_complete(dates_completes) pointages.append(_pointage("2026-07-20")) # lundi suivant, rien pointé — mais la semaine est déjà à l'équilibre result = compute_week(pointages, [], HEURES_JOUR_MIN) lundi_suivant = result["jours"][5] - assert lundi_suivant["cible_matin"] is None - assert lundi_suivant["cible_aprem"] is None + assert result["extra_min"] == 0 + assert lundi_suivant["cible_matin"] == "12:00" + assert lundi_suivant["cible_aprem"] == "18:18" -def test_compute_week_jour_courant_non_entame_rythme_normal_suffit(freeze_today): +def test_compute_week_jour_courant_non_entame_propose_le_rythme_normal(freeze_today): """"Aujourd'hui" sans rien pointé n'est pas traité comme un jour futur figé : ses deux demi-journées entrent dans la répartition. Ici le manque (468 min) correspond exactement - au nominal des deux demi-journées restantes : le rythme normal suffit, pas de cible.""" + au nominal des deux demi-journées restantes : le rythme normal suffit, mais les horaires + visés matin et aprem restent proposés.""" dates_completes = ["2026-07-13", "2026-07-14", "2026-07-15", "2026-07-16"] pointages = _semaine_complete(dates_completes) pointages.append(_pointage("2026-07-17")) result = compute_week(pointages, [], HEURES_JOUR_MIN) vendredi = result["jours"][4] - assert vendredi["cible_matin"] is None - assert vendredi["cible_aprem"] is None + assert result["extra_min"] == 0 + assert vendredi["cible_matin"] == "12:00" + assert vendredi["cible_aprem"] == "18:18" def test_compute_week_demi_journee_en_cours_pas_de_double_comptage(freeze_today): @@ -196,8 +201,9 @@ def test_compute_week_demi_journee_en_cours_pas_de_double_comptage(freeze_today) result = compute_week(pointages, [], HEURES_JOUR_MIN) vendredi = result["jours"][4] assert result["extra_min"] == 60 - # matin saturé au nominal (09:00→12:00), pas de cible ; aprem : 13:30 + 288 + 60 = 19:18 - assert vendredi["cible_matin"] is None + # matin en cours : sa cible correspond au planning nominal (09:00→12:00), pas de rattrapage + # possible (saturé au plafond pause) ; aprem : 13:30 + 288 + 60 = 19:18 + assert vendredi["cible_matin"] == "12:00" assert vendredi["cible_aprem"] == "19:18" @@ -223,7 +229,9 @@ def test_compute_week_cible_matin_ne_mange_jamais_la_pause_de_midi(freeze_today) # 120 min de retard + 54 min de nominal matin qui ne tiennent pas avant la pause # (et ne peuvent pas basculer sur l'après-midi, en congé) assert result["extra_min"] == 174 - assert vendredi["cible_matin"] is None + # Le matin (seul créneau dispo, aprem en congé) est saturé à son plafond 09:00→12:00 : + # cible proposée au rythme normal, mais le rattrapage (174 min) ne tient pas. + assert vendredi["cible_matin"] == "12:00" assert vendredi["cible_aprem"] is None # congé @@ -245,8 +253,8 @@ def test_compute_week_surplus_creneau_sature_reporte_sur_les_autres(freeze_today result = compute_week(pointages, [], HEURES_JOUR_MIN) vendredi = result["jours"][4] assert result["extra_min"] == 80 - # matin saturé au nominal, pas de cible ; aprem : 13:30 + 288 + 80 = 19:38 - assert vendredi["cible_matin"] is None + # matin saturé au nominal (cible au rythme normal 09:00→12:00) ; aprem : 13:30 + 288 + 80 = 19:38 + assert vendredi["cible_matin"] == "12:00" assert vendredi["cible_aprem"] == "19:38" @@ -327,7 +335,7 @@ def test_compute_week_depart_vise_plafond_souple(freeze_today): # vendredi aprem : 13:30 + 234 + (36 + 9) = 18:09 ; lundi aprem : 13:30 + 288 + 9 = 18:27 # (sans départ visé, lundi finirait à 18:45) assert vendredi["cible_aprem"] == "18:09" - assert lundi["cible_matin"] is None + assert lundi["cible_matin"] == "12:00" # matin au rythme normal (pas de rattrapage pour ce créneau) assert lundi["cible_aprem"] == "18:27"