Respecte la pause déjeuner dans les préconisations et ajoute les heures visées
La cible matin ne dépasse plus jamais matin_fin, garantissant la pause de midi ; le nominal qui déborde bascule sur l'après-midi du même jour. Ajoute deux réglages optionnels par utilisateur (arrivée visée le matin, départ visé le soir) qui orientent la répartition du rattrapage en deux passes : d'abord dans le cadre visé, puis au-delà si nécessaire. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -180,8 +180,9 @@ def test_compute_week_demi_journee_en_cours_pas_de_double_comptage(freeze_today)
|
||||
comme capacité restante — ce qui masquait le rattrapage pendant la journée.
|
||||
|
||||
Lundi en retard de 60 min, mardi-jeudi complets, vendredi (aujourd'hui, gelé à
|
||||
10:30) matin en cours depuis 09:00. Les 60 min doivent être réparties : le matin
|
||||
est plafonné par la pause déjeuner (max +36), donc 30/30 passe sans saturation.
|
||||
10:30) matin en cours depuis 09:00. Le matin s'arrête à la pause de midi
|
||||
(nominal plafonné à 12:00, l'excédent bascule sur l'après-midi) : les 60 min
|
||||
de rattrapage vont entièrement sur l'après-midi.
|
||||
"""
|
||||
pointages = [
|
||||
_pointage("2026-07-13", "09:00", "12:24", "13:30", "16:54"), # -60 min
|
||||
@ -193,18 +194,19 @@ 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 : 09:00 + 234 nominal + 30 = 13:24 ; aprem : 13:30 + 234 + 30 = 17:54
|
||||
assert vendredi["cible_matin"] == "13:24"
|
||||
assert vendredi["cible_aprem"] == "17:54"
|
||||
# matin saturé au nominal (09:00→12:00), pas de cible ; aprem : 13:30 + 288 + 60 = 19:18
|
||||
assert vendredi["cible_matin"] is None
|
||||
assert vendredi["cible_aprem"] == "19:18"
|
||||
|
||||
|
||||
def test_compute_week_cible_matin_plafonnee_a_la_pause_dejeuner(freeze_today):
|
||||
"""La cible matin ne dépasse jamais la reprise de l'après-midi : un créneau
|
||||
saturé reçoit sa capacité max, le surplus reste non alloué (pas de cible absurde).
|
||||
def test_compute_week_cible_matin_ne_mange_jamais_la_pause_de_midi(freeze_today):
|
||||
"""La cible matin ne dépasse jamais matin_fin : la pause de midi (12:00→13:30)
|
||||
reste libre, même quand le retard ne peut pas être rattrapé ailleurs.
|
||||
|
||||
Vendredi en congé après-midi : seule sa matinée reste (nominal 234, capacité
|
||||
09:00→13:30 = 270, soit +36 max). Avec 120 min de retard, la cible est plafonnée
|
||||
à 13:30 au lieu d'un impossible 14:54.
|
||||
Vendredi en congé après-midi : seule sa matinée reste, plafonnée à 09:00→12:00
|
||||
(180 min, sous les 234 nominales). Avec 120 min de retard sur lundi, aucune
|
||||
cible n'est proposée (le créneau est déjà saturé) plutôt qu'une sortie à 13:30
|
||||
qui supprimerait la pause ; le manque non plaçable reste visible dans extra.
|
||||
"""
|
||||
conges = [{"date": "2026-07-17", "type": "aprem"}]
|
||||
pointages = [
|
||||
@ -216,17 +218,20 @@ def test_compute_week_cible_matin_plafonnee_a_la_pause_dejeuner(freeze_today):
|
||||
]
|
||||
result = compute_week(pointages, conges, HEURES_JOUR_MIN)
|
||||
vendredi = result["jours"][4]
|
||||
assert result["extra_min"] == 120
|
||||
assert vendredi["cible_matin"] == "13:30"
|
||||
# 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
|
||||
assert vendredi["cible_aprem"] is None # congé
|
||||
|
||||
|
||||
def test_compute_week_surplus_creneau_sature_reporte_sur_les_autres(freeze_today):
|
||||
"""Quand un créneau matin sature (plafond pause déjeuner), le surplus doit se
|
||||
"""Quand le créneau matin sature (plafond pause de midi), le rattrapage doit se
|
||||
reporter sur les créneaux encore ouverts au lieu d'être perdu.
|
||||
|
||||
80 min de retard sur 2 demi-journées de vendredi : réparti 40/40, mais le matin
|
||||
plafonne à +36 — les 4 min restantes basculent sur l'après-midi (+44).
|
||||
80 min de retard, vendredi entièrement vide : le matin est déjà saturé à son
|
||||
plafond 09:00→12:00 (nominal 180, excédent 54 basculé sur l'après-midi qui
|
||||
passe à 288), donc tout le rattrapage va sur l'après-midi.
|
||||
"""
|
||||
pointages = [
|
||||
_pointage("2026-07-13", "09:00", "12:24", "13:30", "16:34"), # -80 min
|
||||
@ -238,9 +243,90 @@ 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é : 09:00 + 270 = 13:30 ; aprem : 13:30 + 234 + 44 = 18:08
|
||||
assert vendredi["cible_matin"] == "13:30"
|
||||
assert vendredi["cible_aprem"] == "18:08"
|
||||
# matin saturé au nominal, pas de cible ; aprem : 13:30 + 288 + 80 = 19:38
|
||||
assert vendredi["cible_matin"] is None
|
||||
assert vendredi["cible_aprem"] == "19:38"
|
||||
|
||||
|
||||
def test_compute_week_cible_matin_possible_quand_la_plage_matin_a_de_la_marge(freeze_today):
|
||||
"""Avec des plages personnalisées où la matinée obligatoire est plus longue que
|
||||
le nominal, une cible matin peut être proposée — mais jamais au-delà de
|
||||
matin_fin, pour que la pause (matin_fin → reprise) reste libre.
|
||||
|
||||
Matin 08:00→13:00 (capacité 300 > nominal 270). Vendredi en congé après-midi,
|
||||
120 min de retard : le matin reçoit +30 max (plafond 13:00), le reste n'est
|
||||
pas plaçable. La pause 13:00→14:00 reste intacte.
|
||||
"""
|
||||
plages = {
|
||||
"matin_debut": "08:00", "matin_fin": "13:00",
|
||||
"aprem_debut": "14:00", "aprem_fin": "18:00",
|
||||
"pause_dejeuner_fin": "14:00",
|
||||
}
|
||||
heures_jour = 540
|
||||
conges = [{"date": "2026-07-17", "type": "aprem"}]
|
||||
pointages = [
|
||||
_pointage("2026-07-13", "08:00", "13:00", "14:00", "16:00"), # -120 min
|
||||
_pointage("2026-07-14", "08:00", "13:00", "14:00", "18:00"),
|
||||
_pointage("2026-07-15", "08:00", "13:00", "14:00", "18:00"),
|
||||
_pointage("2026-07-16", "08:00", "13:00", "14:00", "18:00"),
|
||||
_pointage("2026-07-17"),
|
||||
]
|
||||
result = compute_week(pointages, conges, heures_jour, plages)
|
||||
vendredi = result["jours"][4]
|
||||
assert result["extra_min"] == 120
|
||||
# 08:00 + 270 nominal + 30 (saturé au plafond matin_fin) = 13:00
|
||||
assert vendredi["cible_matin"] == "13:00"
|
||||
assert vendredi["cible_aprem"] is None # congé
|
||||
|
||||
|
||||
def test_compute_week_arrivee_visee_etend_le_creneau_matin(freeze_today):
|
||||
"""L'arrivée visée sert de début projeté du matin : en visant 08:00, le créneau
|
||||
matin gagne de la capacité (08:00→12:00 = 240 ≥ 234 nominal) et peut recevoir
|
||||
du rattrapage — toujours sans dépasser matin_fin.
|
||||
"""
|
||||
plages = {"arrivee_visee": "08:00"}
|
||||
conges = [{"date": "2026-07-17", "type": "aprem"}]
|
||||
pointages = [
|
||||
_pointage("2026-07-13", "09:00", "12:24", "13:30", "15:54"), # -120 min
|
||||
_pointage("2026-07-14", "09:00", "12:24", "13:30", "17:54"),
|
||||
_pointage("2026-07-15", "09:00", "12:24", "13:30", "17:54"),
|
||||
_pointage("2026-07-16", "09:00", "12:24", "13:30", "17:54"),
|
||||
_pointage("2026-07-17"),
|
||||
]
|
||||
result = compute_week(pointages, conges, HEURES_JOUR_MIN, plages)
|
||||
vendredi = result["jours"][4]
|
||||
assert result["extra_min"] == 120
|
||||
# 08:00 + 234 nominal + 6 (saturé au plafond matin_fin) = 12:00
|
||||
assert vendredi["cible_matin"] == "12:00"
|
||||
|
||||
|
||||
def test_compute_week_depart_vise_plafond_souple(freeze_today):
|
||||
"""Le départ visé concentre d'abord le rattrapage sur les créneaux qui tiennent
|
||||
avant cette heure ; il n'est dépassé que si ça ne tient nulle part (plafond souple).
|
||||
|
||||
Vendredi matin pointé 09:00→12:00, son après-midi reste (nominal 234) ; lundi
|
||||
suivant vide (matin 180 saturé, aprem 288). Rattrapage : 54 min. Sans départ
|
||||
visé, réparti 27/27. Avec départ visé 18:00 : l'aprem de vendredi (qui a de la
|
||||
marge avant 18:00) absorbe d'abord 36, puis la 2e passe répartit le reste 9/9.
|
||||
"""
|
||||
plages = {"depart_vise": "18:00"}
|
||||
pointages = [
|
||||
_pointage("2026-07-13", "09:00", "12:24", "13:30", "17:54"),
|
||||
_pointage("2026-07-14", "09:00", "12:24", "13:30", "17:54"),
|
||||
_pointage("2026-07-15", "09:00", "12:24", "13:30", "17:54"),
|
||||
_pointage("2026-07-16", "09:00", "12:24", "13:30", "17:54"),
|
||||
_pointage("2026-07-17", "09:00", "12:00"),
|
||||
_pointage("2026-07-20"), # lundi suivant, vide
|
||||
]
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN, plages)
|
||||
vendredi = result["jours"][4]
|
||||
lundi = result["jours"][5]
|
||||
assert result["extra_min"] == 54
|
||||
# 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_aprem"] == "18:27"
|
||||
|
||||
|
||||
def test_compute_week_plages_personnalisees(freeze_today):
|
||||
|
||||
Reference in New Issue
Block a user