Propose systématiquement les horaires du matin et de l'après-midi

La colonne Cible affichait un horaire seulement pour les demi-journées
avec du rattrapage à faire ; les demi-journées au rythme normal
n'affichaient rien. Désormais chaque demi-journée restante (matin et
après-midi) propose sa plage entrée → sortie, qu'il y ait du retard ou
non, pour donner un planning complet à l'utilisateur.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
Jacquin Antoine
2026-07-19 23:37:17 +02:00
parent 27bcd7a0b4
commit ef30820c39
2 changed files with 25 additions and 19 deletions

View File

@ -202,8 +202,6 @@ def compute_week(pointages: list[dict], conges: list[dict], heures_jour_min: int
left -= give left -= give
for i, s in enumerate(slots): 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 = s["debut"] + s["nominal"] + adjs[i]
sortie_min = max(sortie_min, matin_fin_min if s["half"] == "matin" else aprem_fin_min) 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}" entree = f"{s['debut'] // 60:02d}:{s['debut'] % 60:02d}"

View File

@ -128,10 +128,11 @@ def test_compute_week_semaine_totalement_vide_compte_les_10_demi_journees(freeze
result = compute_week(pointages, [], HEURES_JOUR_MIN) result = compute_week(pointages, [], HEURES_JOUR_MIN)
assert result["slots_restants"] == 10 assert result["slots_restants"] == 10
assert result["reste_min"] == HEURES_JOUR_MIN * 5 assert result["reste_min"] == HEURES_JOUR_MIN * 5
# Rien n'est encore en retard puisque rien n'a encore été renseigné : le rythme # Pas de retard (rien n'a encore été renseigné) : le rythme normal suffit, extra = 0.
# normal (à répartir librement sur les 10 demi-journées) suffit, pas de cible imposée. # 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 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): 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 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): def test_compute_week_jour_futur_a_egalite_propose_le_rythme_normal(freeze_today):
"""Quand le rythme normal des demi-journées restantes suffit déjà, aucune cible n'est affichée.""" """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"] dates_completes = ["2026-07-13", "2026-07-14", "2026-07-15", "2026-07-16", "2026-07-17"]
pointages = _semaine_complete(dates_completes) pointages = _semaine_complete(dates_completes)
pointages.append(_pointage("2026-07-20")) # lundi suivant, rien pointé — mais la semaine est déjà à l'équilibre 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) result = compute_week(pointages, [], HEURES_JOUR_MIN)
lundi_suivant = result["jours"][5] lundi_suivant = result["jours"][5]
assert lundi_suivant["cible_matin"] is None assert result["extra_min"] == 0
assert lundi_suivant["cible_aprem"] is None 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 """"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 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"] dates_completes = ["2026-07-13", "2026-07-14", "2026-07-15", "2026-07-16"]
pointages = _semaine_complete(dates_completes) pointages = _semaine_complete(dates_completes)
pointages.append(_pointage("2026-07-17")) pointages.append(_pointage("2026-07-17"))
result = compute_week(pointages, [], HEURES_JOUR_MIN) result = compute_week(pointages, [], HEURES_JOUR_MIN)
vendredi = result["jours"][4] vendredi = result["jours"][4]
assert vendredi["cible_matin"] is None assert result["extra_min"] == 0
assert vendredi["cible_aprem"] is None 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): 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) result = compute_week(pointages, [], HEURES_JOUR_MIN)
vendredi = result["jours"][4] vendredi = result["jours"][4]
assert result["extra_min"] == 60 assert result["extra_min"] == 60
# matin saturé au nominal (09:00→12:00), pas de cible ; aprem : 13:30 + 288 + 60 = 19:18 # matin en cours : sa cible correspond au planning nominal (09:00→12:00), pas de rattrapage
assert vendredi["cible_matin"] is None # possible (saturé au plafond pause) ; aprem : 13:30 + 288 + 60 = 19:18
assert vendredi["cible_matin"] == "12:00"
assert vendredi["cible_aprem"] == "19:18" 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 # 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é) # (et ne peuvent pas basculer sur l'après-midi, en congé)
assert result["extra_min"] == 174 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é 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) result = compute_week(pointages, [], HEURES_JOUR_MIN)
vendredi = result["jours"][4] vendredi = result["jours"][4]
assert result["extra_min"] == 80 assert result["extra_min"] == 80
# matin saturé au nominal, pas de cible ; aprem : 13:30 + 288 + 80 = 19:38 # matin saturé au nominal (cible au rythme normal 09:00→12:00) ; aprem : 13:30 + 288 + 80 = 19:38
assert vendredi["cible_matin"] is None assert vendredi["cible_matin"] == "12:00"
assert vendredi["cible_aprem"] == "19:38" 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 # 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) # (sans départ visé, lundi finirait à 18:45)
assert vendredi["cible_aprem"] == "18:09" 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" assert lundi["cible_aprem"] == "18:27"