Le forfait d'astreinte ne compte plus dans les heures travaillées

Le temps travaillé, l'écart du jour et le solde de la semaine ne
restituent plus que les heures pointées. Le forfait des jours AS reste
suivi à part : ligne du jour et récapitulatif d'astreinte, libellés et
documentation ajustés en conséquence.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
This commit is contained in:
Jacquin Antoine
2026-09-09 07:26:28 +02:00
parent 6bd0e91f27
commit 5506f1ba39
7 changed files with 26 additions and 20 deletions

View File

@ -194,9 +194,10 @@ jour, pleine largeur : un menu dépliant fermé par défaut (seuls l'heure de re
et le total restent visibles). Déplié, il offre autant de lignes que nécessaire
(**+ intervention**) sur trois colonnes : **début**, **fin** et description
libre. Une fin antérieure au début (fin < début) désigne une intervention à
cheval sur minuit. Une durée forfaitaire peut être ajoutée au temps travaillé de
chaque jour d'astreinte déclaré (page **Réglages**, « Forfait par jour
d'astreinte »).
cheval sur minuit. Une durée forfaitaire peut être attribuée à chaque jour
d'astreinte déclaré (page **Réglages**, « Forfait par jour d'astreinte ») :
elle ne compte jamais dans les heures travaillées, elle est suivie à part dans
le récapitulatif d'astreinte.
La semaine affiche aussi **samedi et dimanche** (heures dues nulles, lignes
grisées) : on peut y déclarer des astreintes, y saisir les interventions et les

View File

@ -458,7 +458,9 @@ def compute_week(pointages: list[dict], conges: list[dict], heures_jour_min: int
if jour_astreinte_qualifie(p["date"], astreintes) and events else None)
matin_ex, aprem_ex = exclusion_retour(conges_date, retour_min, plages, jour_ferie)
forfait = forfait_as_min if p["date"] in astreintes else 0
travaille = heures_travaillees(p, retour_min) + forfait
# Le forfait d'astreinte ne compte jamais dans les heures travaillées :
# il est suivi à part dans le récapitulatif d'astreinte.
travaille = heures_travaillees(p, retour_min)
du = heures_dues(p["date"], conges, heures_jour_min, plages, retour_min, jour_ferie)
du_full = du + matin_ex + aprem_ex
matin_nom, aprem_nom = nominaux_du_jour(conges_date, du_full, matin_ex, aprem_ex, jour_ferie)

View File

@ -335,7 +335,7 @@ def _build_jour_ctx(date, conges, h_jour, user_id):
matin_ex, aprem_ex = exclusion_retour(conges_date, retour_min, plages, jour_ferie)
forfait = (hhmm_to_minutes(plages["astreinte_forfait"]) if plages.get("astreinte_forfait") else 0) \
if date in astreintes else 0
trav = heures_travaillees(p, retour_min) + forfait
trav = heures_travaillees(p, retour_min) # forfait d'astreinte : hors heures travaillées
du = heures_dues(date, conges, h_jour, plages, retour_min, jour_ferie)
du_full = du + matin_ex + aprem_ex
matin_nom, aprem_nom = nominaux_du_jour(conges_date, du_full, matin_ex, aprem_ex, jour_ferie)
@ -492,8 +492,8 @@ def _as_block_html(date, jour):
hint = (f'<span class="as-hint" title="11h de repos consécutif après la dernière activité, fin arrondie au quart d\'heure entamé">'
f'retour ≥ {_e(jour["retour"])}</span>')
if jour.get("forfait_min"):
hint += (f'<span class="as-hint" title="Forfait réglé dans Réglages, ajouté au temps travaillé">'
f'forfait + {_e(jour["forfait_h"])}</span>')
hint += (f'<span class="as-hint" title="Forfait du jour AS déclaré (réglé dans Réglages) : suivi dans le récapitulatif d&#39;astreinte, hors heures travaillées">'
f'forfait {_e(jour["forfait_h"])}</span>')
total = ""
if jour.get("astreinte_min"):
total = (f'<span class="as-total" title="Décompte par quarts d&#39;heure de l&#39;horloge entamés (:00, :15, :30, :45), chacun compté 15 min entières — compteur séparé, hors heures de semaine">'
@ -1125,8 +1125,9 @@ def settings_plages_save(
@app.post("/settings/astreinte-forfait", response_class=HTMLResponse)
def settings_astreinte_forfait(request: Request, astreinte_forfait: str = Form("")):
"""Durée forfaitaire ajoutée au temps travaillé de chaque jour d'astreinte
déclaré (réglage par utilisateur, stocké avec ses plages)."""
"""Durée forfaitaire attribuée à chaque jour d'astreinte déclaré (réglage par
utilisateur, stocké avec ses plages) : suivie dans le récapitulatif
d'astreinte, jamais comptée dans les heures travaillées."""
user_id = _get_user(request)
if not user_id:
return RedirectResponse("/login", status_code=302)

View File

@ -131,7 +131,7 @@
<span class="as-caret">▸</span> {{ nb_as }} intervention{{ 's' if nb_as != 1 else '' }}
</button>
{% if j.retour_min is not none %}<span class="as-hint" title="11h de repos consécutif après la dernière activité, fin arrondie au quart d'heure entamé">retour ≥ {{ j.retour }}</span>{% endif %}
{% if j.forfait_min %}<span class="as-hint" title="Forfait réglé dans Réglages, ajouté au temps travaillé">forfait + {{ j.forfait_h }}</span>{% endif %}
{% if j.forfait_min %}<span class="as-hint" title="Forfait du jour AS déclaré (Réglages) : suivi dans le récapitulatif d'astreinte, hors heures travaillées">forfait {{ j.forfait_h }}</span>{% endif %}
{% if j.astreinte_min %}<span class="as-total" title="Décompte par quarts d'heure de l'horloge entamés (:00, :15, :30, :45), chacun compté 15 min entières — compteur séparé, hors heures de semaine">astreinte : {{ j.astreinte_h }}</span>{% endif %}
</div>
<div class="as-body">

View File

@ -56,7 +56,7 @@
<div class="as-jour">
<div class="as-jour-head">
<span class="as-jour-date">{{ j.jour }} {{ j.date[8:] }}/{{ j.date[5:7] }}</span>
{% if j.forfait_min %}<span class="as-forfait" title="Forfait du jour AS déclaré (réglé dans Réglages) : ajouté au temps travaillé de la semaine, hors compteurs d'astreinte">forfait + {{ j.forfait_h }}</span>{% endif %}
{% if j.forfait_min %}<span class="as-forfait" title="Forfait du jour AS déclaré (réglé dans Réglages) : suivi dans le récapitulatif d'astreinte, jamais compté dans les heures travaillées ni dans les compteurs pondérés">forfait {{ j.forfait_h }}</span>{% endif %}
{% if j.total_min %}<span class="as-jour-total">astreinte {{ j.total_h }} → {{ j.total_pondere_h }} pondérées</span>{% endif %}
</div>
{% for ev in j.events %}
@ -76,7 +76,7 @@
{% endfor %}
<div class="as-totaux">
{% for c in result.astreinte.categories %}<span class="as-cat">{{ c.nom }} : {{ c.min_h }} ×{{ c.facteur_txt }} = {{ c.pondere_h }}</span>{% endfor %}
{% if result.astreinte.total_forfait_min %}<span class="as-forfait" title="Forfaits des jours AS déclarés : ajoutés au temps travaillé, hors compteurs d'astreinte">forfaits + {{ result.astreinte.total_forfait_h }} <em>(dans le travaillé)</em></span>{% endif %}
{% if result.astreinte.total_forfait_min %}<span class="as-forfait" title="Forfaits des jours AS déclarés : suivis à part, jamais comptés dans les heures travaillées">forfaits {{ result.astreinte.total_forfait_h }} <em>(hors heures travaillées)</em></span>{% endif %}
<span class="as-grand">total {{ result.astreinte.total_h }} → {{ result.astreinte.total_pondere_h }} pondérées</span>
</div>
</div>

View File

@ -125,9 +125,10 @@ function asDelRatio(btn) { btn.closest('.as-ratio-edit').remove(); }
<div class="settings-card">
<span class="hist-label">Forfait par jour d'astreinte</span>
<p class="settings-p">
Durée ajoutée automatiquement au temps travaillé de chaque jour d'astreinte
déclaré (bouton <b>AS</b>), en plus des interventions saisies. Elle compte dans
le bilan de la semaine ; le lendemain de l'astreinte n'est pas concerné.
Durée forfaitaire attribuée à chaque jour d'astreinte déclaré (bouton <b>AS</b>),
en plus des interventions saisies. Elle ne compte jamais dans les heures
travaillées : elle est suivie à part dans le récapitulatif d'astreinte de la
semaine ; le lendemain de l'astreinte n'est pas concerné.
Vide = pas de forfait.
</p>

View File

@ -555,9 +555,10 @@ def test_compute_week_astreinte_samedi_dimanche(freeze_today):
# ── Forfait par jour d'astreinte déclaré ───────────────────────────────────────
def test_compute_week_forfait_ajoute_au_travaille_du_jour_declare(freeze_today):
"""Forfait 1h00 réglé : ajouté au travaillé du jour AS déclaré uniquement
(pas du lendemain), sans toucher aux heures dues."""
def test_compute_week_forfait_hors_heures_travaillees(freeze_today):
"""Forfait 1h00 réglé : attribué au jour AS déclaré uniquement (pas du
lendemain), mais jamais compté dans les heures travaillées — seulement
suivi dans le récapitulatif d'astreinte."""
plages = {**PLAGES, "astreinte_forfait": "01:00"}
pointages = [
_pointage("2026-07-13", "09:00", "12:24", "13:30", "17:54"),
@ -567,10 +568,10 @@ def test_compute_week_forfait_ajoute_au_travaille_du_jour_declare(freeze_today):
result = compute_week(pointages, [], HEURES_JOUR_MIN, plages, ["2026-07-14"])
mardi = result["jours"][1]
assert mardi["forfait_min"] == 60
assert mardi["travaille_min"] == HEURES_JOUR_MIN + 60
assert mardi["travaille_min"] == HEURES_JOUR_MIN # pointé seul, forfait exclu
assert result["jours"][2]["forfait_min"] == 0 # lendemain : rien
assert result["jours"][0]["forfait_min"] == 0 # jour sans astreinte
assert result["total_travaille"] == "16h36" # 2 × 7h48 + 1h
assert result["total_travaille"] == "15h36" # 2 × 7h48, forfait exclu
# récapitulatif d'astreinte : le jour AS apparaît avec son forfait même sans
# intervention ; le forfait est rappelé mais hors compteurs pondérés
ast = result["astreinte"]