Cibles : reprise ancrée au présent et rafraîchissement en direct
L'heure de reprise de pause n'est qu'indicative : le jour même, une
fois l'heure désirée passée, la cible d'un après-midi non commencé
ancre la reprise à maintenant et la sortie s'ajuste au reste à faire —
suivre les cibles mène au solde 0 exact, et aucune cible ne recommande
de reprendre dans le passé.
La page semaine rafraîchit cibles, panneau Aujourd'hui et compteurs
toutes les 30 s, et immédiatement au retour sur l'onglet (les
minuteurs des navigateurs sont gelés en arrière-plan, l'affichage ne
peit plus rester périmé).
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@ -580,8 +580,7 @@ def test_en_cours_sature_matin_pas_de_double_comptage(monkeypatch):
|
||||
dans le travaillé, une fois réintégré au plan — si bien que suivre les
|
||||
cibles sur-livrait de 105 min. Désormais le créneau saturé propose
|
||||
« sortie maintenant », ne réintègre rien, et l'après-midi est allégé
|
||||
d'autant : suivi des cibles → solde 0 (à ~12 min près bornées par les
|
||||
plages : reprise ≥ maintenant, plancher 17:00)."""
|
||||
d'autant : suivi des cibles → solde 0 exactement."""
|
||||
_Freezer.freeze(monkeypatch, 13, 45)
|
||||
pointages = [
|
||||
_pointage("2026-07-13", "09:00", "12:24", "13:30", "17:54"),
|
||||
@ -595,16 +594,16 @@ def test_en_cours_sature_matin_pas_de_double_comptage(monkeypatch):
|
||||
# Sortie du matin = maintenant (285 min déjà faites, créneau saturé)
|
||||
assert vendredi["cible_matin"] == "13:45"
|
||||
# L'après-midi absorbe l'excédent du matin : départ au plancher 17:00
|
||||
# (nominal 288 − 78 de marge) et reprise retardée à maintenant.
|
||||
assert vendredi["cible_aprem_entree"] == "13:45"
|
||||
# (nominal 288 − 93 de marge), reprise ancrée à maintenant puis retardée
|
||||
# de 12 min dans la marge avant le début de présence (14:00).
|
||||
assert vendredi["cible_aprem_entree"] == "13:57"
|
||||
assert vendredi["cible_aprem"] == "17:00"
|
||||
# Suivre les cibles ne sur-livre plus de 105 min : reste ≤ 12 min
|
||||
# (le solde résiduel vient des bornes de plages, pas du calcul).
|
||||
# Suivre les cibles ne sur-livre plus de 105 min : solde exactement 0.
|
||||
suivis = [dict(p) for p in pointages]
|
||||
suivis[4] = _pointage("2026-07-17", "09:00", vendredi["cible_matin"],
|
||||
vendredi["cible_aprem_entree"], vendredi["cible_aprem"])
|
||||
apres = compute_week(suivis, [], HEURES_JOUR_MIN)
|
||||
assert 0 <= apres["solde_min"] <= 12
|
||||
assert apres["solde_min"] == 0
|
||||
|
||||
|
||||
def test_en_cours_sature_aprem_propose_sortie_immediate(monkeypatch):
|
||||
@ -649,6 +648,30 @@ def test_en_cours_non_sature_reste_inchange(monkeypatch):
|
||||
assert apres["solde_min"] == 0
|
||||
|
||||
|
||||
def test_reprise_desiree_depassee_ancree_a_maintenant(monkeypatch):
|
||||
"""La reprise de pause (pause_dejeuner_fin) n'est qu'une heure désirée :
|
||||
le jour même, quand elle est dépassée et que l'après-midi n'a pas commencé,
|
||||
la cible doit ancrer la reprise à maintenant — jamais dans le passé — et
|
||||
la sortie s'ajuste pour que le suivi donne exactement le solde visé."""
|
||||
_Freezer.freeze(monkeypatch, 14, 12)
|
||||
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"), # aujourd'hui 14:12, aprem non repris
|
||||
]
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN)
|
||||
vendredi = result["jours"][4]
|
||||
assert vendredi["cible_aprem_entree"] == "14:12" # maintenant, pas 13:30
|
||||
assert vendredi["cible_aprem"] == "19:00" # 14:12 + 234 nominal + 54 retard
|
||||
suivis = [dict(p) for p in pointages]
|
||||
suivis[4] = _pointage("2026-07-17", "09:00", "12:00",
|
||||
vendredi["cible_aprem_entree"], vendredi["cible_aprem"])
|
||||
apres = compute_week(suivis, [], HEURES_JOUR_MIN)
|
||||
assert apres["solde_min"] == 0
|
||||
|
||||
|
||||
def test_entree_apres_fin_matin_le_surplus_ne_double_compte_pas(monkeypatch):
|
||||
"""Régression : entrée du matin pointée à 12:30 (après la fin de Présence
|
||||
matin), sans sortie, il est 13:45. Aucun créneau matin n'existe (capacité
|
||||
|
||||
@ -113,3 +113,40 @@ def test_forfait_bonifie_journee_affiche_dans_le_recap(monkeypatch):
|
||||
assert "forfait 1h00 → 1h30 pondérées" in r.text # ligne du jour (bloc AS)
|
||||
assert "forfaits 1h00 → 1h30 pondérés" in r.text # totaux du récapitulatif
|
||||
assert "considéré réalisé en journée" in r.text # explication au survol
|
||||
|
||||
def test_refresh_regenerere_les_cibles_en_fonction_de_lheure(monkeypatch):
|
||||
"""Le poll /refresh (toutes les 30 s, et au retour sur l'onglet) régénère
|
||||
les cibles OOB avec l'heure courante : à 14:12, l'après-midi non commencé
|
||||
du vendredi ancre sa reprise à maintenant — jamais à l'heure désirée 13:30
|
||||
passée — et la sortie s'ajuste au reste à faire de la semaine."""
|
||||
class _Frozen14(dt_module.datetime):
|
||||
@classmethod
|
||||
def today(cls):
|
||||
return cls(2026, 7, 17, 14, 12)
|
||||
|
||||
monkeypatch.setattr(main, "datetime", _Frozen14)
|
||||
import calcul
|
||||
monkeypatch.setattr(calcul, "datetime", _Frozen14)
|
||||
cookies = _login("u-ui-refresh")
|
||||
# Lundi-jeudi complets, vendredi matin seulement : reste 4h42,
|
||||
# seul créneau ouvert = vendredi aprem, reprise ancrée à 14:12.
|
||||
for d in ("2026-07-13", "2026-07-14", "2026-07-15", "2026-07-16"):
|
||||
client.post(f"/pointage/{d}", cookies=cookies,
|
||||
data={"matin_entree": "09:00", "matin_sortie": "12:24",
|
||||
"aprem_entree": "13:30", "aprem_sortie": "17:54"},
|
||||
headers={"HX-Request": "true"}, follow_redirects=False)
|
||||
client.post(f"/pointage/{TODAY}", cookies=cookies,
|
||||
data={"matin_entree": "09:00", "matin_sortie": "12:00"},
|
||||
headers={"HX-Request": "true"}, follow_redirects=False)
|
||||
|
||||
r = client.get(f"/refresh/{TODAY}", cookies=cookies, follow_redirects=False)
|
||||
assert r.status_code == 200
|
||||
# Cible du jour régénérée, ancrée à maintenant (14:12), pas à 13:30
|
||||
assert f'id="calc-{TODAY}-cible"' in r.text
|
||||
assert "A 14:12 → 19:00" in r.text
|
||||
# Toutes les cibles de la semaine suivent (swaps OOB), pas seulement le jour
|
||||
for d in ("2026-07-13", "2026-07-14", "2026-07-15", "2026-07-16"):
|
||||
assert f'id="calc-{d}-cible"' in r.text
|
||||
# Le panneau aujourd'hui est rafraîchi avec la même sortie cible
|
||||
assert "today-panel" in r.text
|
||||
assert "19:00" in r.text
|
||||
|
||||
Reference in New Issue
Block a user