Ajoute les jours fériés, l'astreinte week-end 0h-0h et les ratios éditables
- Bouton F : marque un jour férié (dû 0, sans consommer de congé) ; il
qualifie les règles « ferie » et étend la fenêtre d'astreinte du jour.
- Samedi, dimanche et fériés : l'astreinte compte de 0h00 à 0h00 ; un jour
AS déclaré garde sa borne du lendemain 18h pour ne pas tronquer les
interventions nocturnes.
- Ratios d'astreinte éditables à la main dans Réglages (nom, jours, fenêtre,
ratio), avec ajout/suppression de règles et validation.
- Un quart d'heure entamé par deux interventions qui se suivent n'est compté
qu'une fois : les lignes d'événements somment au total du jour.
- Décompte mieux expliqué dans l'interface : quarts d'horloge (:00/:15/:30/:45),
fenêtre couverte par événement, exemple chiffré.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@ -219,7 +219,7 @@ def test_decompte_vendredi_vers_samedi_reste_en_nuit():
|
||||
|
||||
def test_decompte_chevauchement_dedouble():
|
||||
"""10:00→10:20 et 10:10→10:30 se recouvrent : les tranches communes ne comptent
|
||||
qu'une fois dans le total du jour, chaque event garde son propre détail."""
|
||||
qu'une fois, attribuées au premier événement ; les lignes somment au total."""
|
||||
d = decompte_astreinte("2026-07-13", [
|
||||
{"debut": "10:00", "fin": "10:20"},
|
||||
{"debut": "10:10", "fin": "10:30"},
|
||||
@ -227,13 +227,29 @@ def test_decompte_chevauchement_dedouble():
|
||||
assert len(d["events"]) == 2
|
||||
assert d["events"][0]["tranches"] == 2 # tranches 10:00 + 10:15
|
||||
assert d["events"][0]["min"] == 30
|
||||
assert d["events"][1]["tranches"] == 2 # 10:10 aligné sur la tranche 10:00
|
||||
assert d["events"][1]["min"] == 30
|
||||
assert d["events"][1]["tranches"] == 0 # les deux tranches déjà comptées
|
||||
assert d["events"][1]["min"] == 0
|
||||
assert d["tranches"] == 2 # union dédoublonnée
|
||||
assert d["total_min"] == 30
|
||||
assert sum(e["min"] for e in d["events"]) == d["total_min"]
|
||||
assert d["categories"][0]["nom"] == "Journée"
|
||||
|
||||
|
||||
def test_decompte_quart_partage_entre_interventions():
|
||||
"""Deux interventions qui se suivent et finissent/débutent dans le même quart :
|
||||
le quart n'est compté qu'une fois (attribué à la première), les lignes
|
||||
d'événements somment au total du jour."""
|
||||
d = decompte_astreinte("2026-07-13", [
|
||||
{"debut": "22:39", "fin": "23:05"},
|
||||
{"debut": "23:02", "fin": "23:49"},
|
||||
])
|
||||
assert d["events"][0]["tranches"] == 3 # 22:30 + 22:45 + 23:00
|
||||
assert d["events"][1]["tranches"] == 3 # 23:15 + 23:30 + 23:45 (23:00 déjà compté)
|
||||
assert sum(e["tranches"] for e in d["events"]) == d["tranches"] == 6
|
||||
assert d["total_min"] == 90
|
||||
assert d["total_pondere_min"] == 135 # 6 × 15 × 1,5 (Nuit)
|
||||
|
||||
|
||||
def test_decompte_event_incomplet_ignore():
|
||||
"""Un événement sans debut ou sans fin est ignoré."""
|
||||
d = decompte_astreinte("2026-07-13", [{"debut": "22:00"}, {"fin": "23:00"}, {}])
|
||||
@ -426,11 +442,21 @@ def test_load_facteurs_astreinte_defaut_si_absent():
|
||||
|
||||
def test_fenetre_astreinte_jour():
|
||||
"""Le jour déclaré couvre 18:00 → 18:00 le lendemain ; le lendemain n'est
|
||||
couvert que jusqu'à 18:00 ; deux astreintes consécutives se recollent."""
|
||||
assert fenetre_astreinte_jour("2026-07-14", ["2026-07-14"]) == (ASTREINTE_DEBUT_MIN, 24 * 60 + ASTREINTE_DEBUT_MIN)
|
||||
assert fenetre_astreinte_jour("2026-07-15", ["2026-07-14"]) == (0, ASTREINTE_DEBUT_MIN)
|
||||
assert fenetre_astreinte_jour("2026-07-15", ["2026-07-14", "2026-07-15"]) == (0, 24 * 60 + ASTREINTE_DEBUT_MIN)
|
||||
assert fenetre_astreinte_jour("2026-07-16", ["2026-07-14"]) is None
|
||||
couvert que jusqu'à 18:00 ; deux astreintes consécutives se recollent.
|
||||
Samedi, dimanche et férié : journée entière 0h00 → 0h00."""
|
||||
# jours ouvrés non fériés (mercredi 15 → vendredi 17 juillet 2026)
|
||||
assert fenetre_astreinte_jour("2026-07-15", ["2026-07-15"]) == (ASTREINTE_DEBUT_MIN, 24 * 60 + ASTREINTE_DEBUT_MIN)
|
||||
assert fenetre_astreinte_jour("2026-07-16", ["2026-07-15"]) == (0, ASTREINTE_DEBUT_MIN)
|
||||
assert fenetre_astreinte_jour("2026-07-16", ["2026-07-15", "2026-07-16"]) == (0, 24 * 60 + ASTREINTE_DEBUT_MIN)
|
||||
assert fenetre_astreinte_jour("2026-07-17", ["2026-07-15"]) is None
|
||||
# samedi et dimanche : journée entière dès 0h00, déclarés ou lendemain
|
||||
# d'astreinte (le jour AS déclaré garde sa borne du lendemain 18:00)
|
||||
assert fenetre_astreinte_jour("2026-07-18", ["2026-07-18"]) == (0, 24 * 60 + ASTREINTE_DEBUT_MIN)
|
||||
assert fenetre_astreinte_jour("2026-07-19", ["2026-07-18"]) == (0, 24 * 60)
|
||||
# férié légal (14 juillet) : journée entière même en milieu de semaine
|
||||
assert fenetre_astreinte_jour("2026-07-14", ["2026-07-13"]) == (0, 24 * 60)
|
||||
# férié marqué à la main : idem
|
||||
assert fenetre_astreinte_jour("2026-07-15", ["2026-07-14"], {"2026-07-15"}) == (0, 24 * 60)
|
||||
|
||||
|
||||
def test_decompte_hors_fenetre_18h_18h_ignore():
|
||||
@ -454,21 +480,58 @@ def test_decompte_hors_fenetre_18h_18h_ignore():
|
||||
|
||||
|
||||
def test_compute_week_intervention_avant_18h_hors_astreinte(freeze_today):
|
||||
"""Mardi AS : l'event 16:00→17:00 (avant 18:00) ne compte pas ; le même
|
||||
créneau à 19:00→20:00 (dans la fenêtre) compte 1h (Journée ×1)."""
|
||||
"""Mercredi ouvré AS : l'event 16:00→17:00 (avant 18:00) ne compte pas ; le
|
||||
même créneau à 19:00→20:00 (dans la fenêtre) compte 1h (Journée ×1)."""
|
||||
pointages = [
|
||||
_pointage("2026-07-13"), _pointage("2026-07-16"), _pointage("2026-07-17"),
|
||||
_pointage("2026-07-14", astreinte_events=[{"debut": "16:00", "fin": "17:00"}]),
|
||||
_pointage("2026-07-15"),
|
||||
_pointage("2026-07-13"), _pointage("2026-07-14"), _pointage("2026-07-16"),
|
||||
_pointage("2026-07-17"),
|
||||
_pointage("2026-07-15", astreinte_events=[{"debut": "16:00", "fin": "17:00"}]),
|
||||
]
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN, PLAGES, ["2026-07-14"])
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN, PLAGES, ["2026-07-15"])
|
||||
assert result["astreinte"]["total_min"] == 0
|
||||
assert result["jours"][1]["astreinte_min"] == 0
|
||||
|
||||
pointages[3] = _pointage("2026-07-14", astreinte_events=[{"debut": "19:00", "fin": "20:00"}])
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN, PLAGES, ["2026-07-14"])
|
||||
assert result["jours"][2]["astreinte_min"] == 0
|
||||
# le même créneau le soir, dans la fenêtre 18:00 → 18:00, compte (Journée ×1)
|
||||
pointages[4] = _pointage("2026-07-15", astreinte_events=[{"debut": "19:00", "fin": "20:00"}])
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN, PLAGES, ["2026-07-15"])
|
||||
assert result["astreinte"]["total_min"] == 60
|
||||
assert result["astreinte"]["total_pondere_min"] == 120 # 14 juillet : férié ×2
|
||||
assert result["astreinte"]["total_pondere_min"] == 60
|
||||
|
||||
|
||||
def test_compute_week_astreinte_weekend_journee_entiere(freeze_today):
|
||||
"""Vendredi AS : le samedi compte de 0h00 à 0h00 — l'intervention 20:00→21:00
|
||||
du samedi (après la borne 18:00 des jours ouvrés) est de l'astreinte (Nuit ×1,5)."""
|
||||
pointages = [
|
||||
_pointage("2026-07-13"), _pointage("2026-07-14"), _pointage("2026-07-15"),
|
||||
_pointage("2026-07-16"),
|
||||
_pointage("2026-07-17", astreinte_events=[{"debut": "20:00", "fin": "21:00"}]),
|
||||
]
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN, PLAGES, ["2026-07-17"])
|
||||
# l'événement saisi sur le vendredi est dans sa fenêtre 18:00 → 18:00
|
||||
vendredi = result["jours"][4]
|
||||
assert vendredi["astreinte_min"] == 60
|
||||
|
||||
|
||||
def test_decompte_fenetre_samedi_soir_apres_18h():
|
||||
"""Samedi lendemain d'astreinte : 20:00→21:00 compte (journée entière),
|
||||
4 tranches en Nuit ×1,5 = 1h30 pondérées."""
|
||||
fen = fenetre_astreinte_jour("2026-07-18", ["2026-07-17"])
|
||||
d = decompte_astreinte("2026-07-18", [{"debut": "20:00", "fin": "21:00"}], None, fen)
|
||||
assert d["total_min"] == 60
|
||||
assert d["categories"][0]["nom"] == "Nuit"
|
||||
assert d["total_pondere_min"] == 90
|
||||
|
||||
|
||||
def test_compute_week_ferie_journee_entiere_ratio_x2(freeze_today):
|
||||
"""Lundi AS, intervention le mardi 14 juillet (férié légal) : journée entière
|
||||
0h00 → 0h00, ratio Dimanche / férié ×2 (10:00→12:00 = 2h → 4h pondérées)."""
|
||||
pointages = [
|
||||
_pointage("2026-07-13"),
|
||||
_pointage("2026-07-14", astreinte_events=[{"debut": "10:00", "fin": "12:00"}]),
|
||||
_pointage("2026-07-15"), _pointage("2026-07-16"), _pointage("2026-07-17"),
|
||||
]
|
||||
result = compute_week(pointages, [], HEURES_JOUR_MIN, PLAGES, ["2026-07-13"])
|
||||
assert result["astreinte"]["total_min"] == 120
|
||||
assert result["astreinte"]["total_pondere_min"] == 240 # férié ×2
|
||||
|
||||
|
||||
def test_compute_week_astreinte_samedi_dimanche(freeze_today):
|
||||
|
||||
Reference in New Issue
Block a user