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:
@ -7,6 +7,8 @@ DEFAULT_PLAGES = {
|
||||
"aprem_debut": "14:00", # présence obligatoire à partir de cette heure l'après-midi
|
||||
"aprem_fin": "17:00", # présence obligatoire jusqu'à cette heure l'après-midi (plancher de sortie)
|
||||
"pause_dejeuner_fin": "13:30", # reprise par défaut projetée tant que l'entrée aprem n'est pas saisie
|
||||
"arrivee_visee": "", # arrivée du matin visée pour les préconisations (vide = matin_debut)
|
||||
"depart_vise": "", # départ du soir visé, plafond souple des cibles aprem (vide = aucun)
|
||||
}
|
||||
|
||||
|
||||
@ -75,6 +77,8 @@ def compute_week(pointages: list[dict], conges: list[dict], heures_jour_min: int
|
||||
matin_fin_min = hhmm_to_minutes(plages["matin_fin"])
|
||||
aprem_fin_min = hhmm_to_minutes(plages["aprem_fin"])
|
||||
pause_dejeuner_fin_min = hhmm_to_minutes(plages["pause_dejeuner_fin"])
|
||||
arrivee_visee_min = hhmm_to_minutes(plages["arrivee_visee"]) if plages.get("arrivee_visee") else matin_debut_min
|
||||
depart_vise_min = hhmm_to_minutes(plages["depart_vise"]) if plages.get("depart_vise") else None
|
||||
|
||||
total_travaille = 0
|
||||
total_du = 0
|
||||
@ -117,8 +121,13 @@ def compute_week(pointages: list[dict], conges: list[dict], heures_jour_min: int
|
||||
jour["cumul_visible"] = jour["is_complete"] and cumul_chain_ok
|
||||
|
||||
# Préconisations : répartit le manque restant à parts égales sur les demi-journées
|
||||
# restantes, dans la limite de la capacité de chaque créneau (le matin est plafonné
|
||||
# à la reprise de l'après-midi, l'après-midi à minuit).
|
||||
# restantes, dans la limite de la capacité de chaque créneau. La pause de midi
|
||||
# (matin_fin → reprise, 12:00 → 13:30 par défaut) est intouchable : la cible
|
||||
# matin ne dépasse jamais matin_fin, et le nominal matin qui n'y tient pas
|
||||
# bascule sur l'après-midi du même jour. Le matin démarre à l'arrivée visée
|
||||
# (sinon matin_debut) ; l'après-midi vise le départ du soir configuré (plafond
|
||||
# souple : dépassé seulement si le rattrapage ne tient nulle part avant), avec
|
||||
# minuit comme limite dure.
|
||||
for jour in jours:
|
||||
jour["cible_matin"] = None
|
||||
jour["cible_aprem"] = None
|
||||
@ -154,30 +163,41 @@ def compute_week(pointages: list[dict], conges: list[dict], heures_jour_min: int
|
||||
matin_done = bool(jour.get("matin_entree") and jour.get("matin_sortie")) or "matin" in cg or "jour" in cg
|
||||
aprem_done = bool(jour.get("aprem_entree") and jour.get("aprem_sortie")) or "aprem" in cg or "jour" in cg
|
||||
if not matin_done:
|
||||
debut = hhmm_to_minutes(jour["matin_entree"]) if jour.get("matin_entree") else matin_debut_min
|
||||
slots.append({"jour": jour, "half": "matin", "nominal": matin_nominal,
|
||||
"debut": debut, "cap": max(0, pause_dejeuner_fin_min - debut)})
|
||||
debut = hhmm_to_minutes(jour["matin_entree"]) if jour.get("matin_entree") else arrivee_visee_min
|
||||
reprise = hhmm_to_minutes(jour["aprem_entree"]) if jour.get("aprem_entree") else pause_dejeuner_fin_min
|
||||
cap = max(0, min(matin_fin_min, reprise) - debut)
|
||||
# Le nominal matin qui ne tient pas avant la pause bascule sur l'après-midi
|
||||
# du même jour (s'il est encore ouvert), sinon dans le rattrapage global.
|
||||
surplus = max(0, matin_nominal - cap)
|
||||
if surplus and not aprem_done:
|
||||
aprem_nominal += surplus
|
||||
slots.append({"jour": jour, "half": "matin", "nominal": min(matin_nominal, cap),
|
||||
"debut": debut, "cap": cap, "cap_pref": cap})
|
||||
if not aprem_done:
|
||||
debut = hhmm_to_minutes(jour["aprem_entree"]) if jour.get("aprem_entree") else pause_dejeuner_fin_min
|
||||
cap = max(0, 23 * 60 + 59 - debut)
|
||||
cap_pref = min(cap, max(0, depart_vise_min - debut)) if depart_vise_min else cap
|
||||
slots.append({"jour": jour, "half": "aprem", "nominal": aprem_nominal,
|
||||
"debut": debut, "cap": max(0, 23 * 60 + 59 - debut)})
|
||||
"debut": debut, "cap": cap, "cap_pref": cap_pref})
|
||||
|
||||
extra_total = max(0, restant_planif - sum(s["nominal"] for s in slots)) if slots else 0
|
||||
slots_restants = len(slots)
|
||||
|
||||
# Répartition équitable du rattrapage, plafonnée par la capacité de chaque créneau :
|
||||
# le surplus d'un créneau saturé se reporte sur les autres.
|
||||
# Répartition équitable du rattrapage, plafonnée par la capacité de chaque créneau,
|
||||
# en deux passes : d'abord dans le cadre visé (avant le départ du soir), puis
|
||||
# au-delà si ça ne tient pas. Le surplus d'un créneau saturé se reporte sur les autres.
|
||||
adjs = [0] * slots_restants
|
||||
left = extra_total
|
||||
while left > 0:
|
||||
open_idx = [i for i, s in enumerate(slots) if adjs[i] < s["cap"] - s["nominal"]]
|
||||
if not open_idx:
|
||||
break # tous les créneaux saturés : rattrapage partiellement impossible cette semaine
|
||||
base, rem = divmod(left, len(open_idx))
|
||||
for k, i in enumerate(open_idx):
|
||||
give = min(slots[i]["cap"] - slots[i]["nominal"] - adjs[i], base + (1 if k < rem else 0))
|
||||
adjs[i] += give
|
||||
left -= give
|
||||
for cap_key in ("cap_pref", "cap"):
|
||||
while left > 0:
|
||||
open_idx = [i for i, s in enumerate(slots) if adjs[i] < s[cap_key] - s["nominal"]]
|
||||
if not open_idx:
|
||||
break # tous les créneaux saturés à ce plafond : passe suivante, ou rattrapage impossible
|
||||
base, rem = divmod(left, len(open_idx))
|
||||
for k, i in enumerate(open_idx):
|
||||
give = min(slots[i][cap_key] - slots[i]["nominal"] - adjs[i], base + (1 if k < rem else 0))
|
||||
adjs[i] += give
|
||||
left -= give
|
||||
|
||||
for i, s in enumerate(slots):
|
||||
if adjs[i] <= 0:
|
||||
|
||||
Reference in New Issue
Block a user