Files
pointeuse-optimisator/app/templates/_soldes.html
Jacquin Antoine 45b450c1ca Préconisations d'horaire : cascade d'avance et correctif de double comptage
Une avance que les départs du soir ne peuvent pas écouler (plancher de
présence) s'écoule désormais en cascade miroir du rattrapage : arrivées
du matin retardées, puis retours de midi, toujours bornés par les plages
de présence.

Une demi-journée en cours qui dépasse son nominal (ex. sortie de midi
oubliée) comptait son temps écoulé deux fois : suivre les cibles
sur-livrait jusqu'à 1h45 par semaine. Le créneau saturé propose
maintenant « sortir maintenant » et l'écart se reporte sur les autres
créneaux ; plus de retard fantôme affiché.

Le README liste désormais l'ensemble des règles de préconisation.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-11 14:04:37 +02:00

120 lines
7.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div id="soldes-section" hx-swap-oob="true">
{% if result.today %}
{% set jt = result.today.jour %}
<div class="today-panel">
<div class="today-main">
<div class="today-eyebrow">Aujourd'hui · {{ result.today.nom }} {{ jt.date[8:] }}/{{ jt.date[5:7] }}</div>
{% if 'jour' in jt.conge %}
<div class="today-cible today-neutral">En congé aujourd'hui</div>
{% elif jt.cible_aprem %}
<div class="today-cible">{{ jt.cible_aprem }}</div>
<div class="today-sub">sortie cible pour tenir le rythme · entrée {{ jt.cible_aprem_entree }}</div>
{% elif jt.cible_matin %}
<div class="today-cible">{{ jt.cible_matin }}</div>
<div class="today-sub">sortie cible du matin · entrée {{ jt.cible_matin_entree }}</div>
{% elif jt.du_min == 0 %}
<div class="today-cible today-neutral">Aucune heure due aujourd'hui</div>
{% else %}
<div class="today-cible today-neutral">Saisissez vos heures du jour</div>
{% endif %}
</div>
{% if result.today.pct is not none %}
<div class="today-prog">
<div class="today-prog-head">
<span>Semaine · <b>{{ result.today.trav_total }}</b> / {{ result.today.du_total }}</span>
<span>Solde <b class="{% if result.solde_min > 0 %}stat-pos{% elif result.solde_min < 0 %}stat-neg{% endif %}">{% if result.solde_min > 0 %}+{% endif %}{{ result.solde }}</b></span>
</div>
<div class="today-bar">
<div class="today-bar-fill{% if result.solde_min < 0 %} short{% elif result.reste_min == 0 %} full-pos{% endif %}" style="width: {{ result.today.pct }}%"></div>
</div>
</div>
{% endif %}
</div>
{% endif %}
<div class="soldes-wrap">
<div class="soldes">
<div class="solde-item">
<div class="solde-eyebrow">Travaillé</div>
<div class="solde-num neutral">{{ result.total_travaille }}</div>
</div>
<div class="solde-item">
<div class="solde-eyebrow">Dû par jour</div>
<div class="solde-num neutral">{{ result.heures_jour }}</div>
</div>
<div class="solde-item">
<div class="solde-eyebrow">Dû cette semaine</div>
<div class="solde-num neutral">{{ result.total_du }}</div>
</div>
<div class="solde-item {% if result.solde_min > 0 %}item-pos{% elif result.solde_min < 0 %}item-neg{% endif %}">
<div class="solde-eyebrow">Solde semaine</div>
<div class="solde-num {% if result.solde_min > 0 %}pos{% elif result.solde_min < 0 %}neg{% endif %}">
{% if result.solde_min > 0 %}+{% endif %}{{ result.solde }}
</div>
</div>
</div>
{% if result.slots_restants > 0 %}
<div class="reste-info">
{% if result.extra_min > 0 %}
Reste à faire : <strong>{{ result.reste }}</strong>
sur {{ result.slots_restants }} demi-journée{{ 's' if result.slots_restants > 1 else '' }} restante{{ 's' if result.slots_restants > 1 else '' }}
— à rattraper : <strong class="reste-extra">+{{ result.extra }}</strong> (~+{{ result.moyenne_extra }} chacune)
{% elif result.avance_min > 0 %}
Reste à faire : <strong>{{ result.reste }}</strong>
sur {{ result.slots_restants }} demi-journée{{ 's' if result.slots_restants > 1 else '' }} restante{{ 's' if result.slots_restants > 1 else '' }}
— avance à écouler : <strong>{{ result.avance }}</strong>, répartie sur les départs du soir, puis arrivées et retours de midi, pour finir la semaine à 0.
{% elif result.reste_min > 0 %}
Reste à faire : <strong>{{ result.reste }}</strong>
sur {{ result.slots_restants }} demi-journée{{ 's' if result.slots_restants > 1 else '' }} restante{{ 's' if result.slots_restants > 1 else '' }}
— le rythme habituel suffit, rien à rattraper.
{% else %}
Vous êtes à jour ou en avance — rien à rattraper cette semaine.
{% endif %}
</div>
{% elif result.solde_min < 0 %}
<div class="reste-info">
Semaine terminée avec <strong class="reste-extra">{{ result.solde }}</strong> de manque —
complétez ou corrigez les saisies, ou rattrapez sur une autre semaine.
</div>
{% endif %}
</div>
{% if result.astreinte and result.astreinte.actif %}
<div class="as-decompte">
<div class="as-decompte-title">Astreinte — compteur séparé, hors heures de semaine</div>
<div class="as-decompte-help">
Décompte par <b>durée réelle arrondie au quart d'heure supérieur</b> : 2, 5 ou
12 min réelles → <b>15 min</b> ; 22 ou 28 → <b>30 min</b> — même si l'arrondi ne
tombe pas juste sur un quart d'heure de l'horloge (:00, :15, :30, :45). Les
minutes réelles communes à deux interventions ne comptent qu'<b>une seule
fois</b>. Ex. 22:39 → 23:49 = 1h10 réelles → arrondies à 1h15.
</div>
{% for j in result.astreinte.jours %}
<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), considéré réalisé en journée du jour : pondéré par le facteur journée correspondant ({{ j.forfait_regle }} ×{{ j.forfait_facteur_txt }}), jamais compté dans les heures travaillées">forfait {{ j.forfait_h }}{% if j.forfait_pondere_min != j.forfait_min %} → {{ j.forfait_pondere_h }} pondérées{% endif %}</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 %}
<div class="as-ev">
<span class="as-ev-h">{{ ev.debut }} → {{ ev.fin }}</span>
{% if ev.desc %}<span class="as-ev-desc">{{ ev.desc }}</span>{% endif %}
{% if ev.tranches %}
<span class="as-ev-q" title="Durée réelle arrondie au quart d'heure supérieur (2, 5 ou 12 min → 15 ; 22 ou 28 → 30), même sans tomber sur un quart d'heure de l'horloge ; les minutes déjà comptées par une intervention précédente ne sont pas recomptées">{{ ev.reel_h }} réelles{% if ev.min > ev.reel_min %} → arrondi {{ ev.min_h }}{% endif %} ({{ ev.arrondi_debut }} → {{ ev.arrondi_fin }})</span>
{% for d in ev.details %}<span class="as-ev-cat">{{ d.nom }} ×{{ d.facteur_txt }}</span>{% if not loop.last %} + {% endif %}{% endfor %}
<span class="as-ev-pond">pondéré → {{ ev.pondere_h }}</span>
{% else %}
<span class="as-ev-q" title="Toutes les minutes réelles de cette intervention sont déjà comptées dans une intervention précédente (chevauchement)">0 min — déjà compté</span>
{% endif %}
</div>
{% endfor %}
</div>
{% 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, considérés réalisés en journée de leur jour : pondérés par le facteur journée (semaine ×1, sam ×1,5, dim/férié ×2 par défaut) — jamais comptés dans les heures travaillées">forfaits {{ result.astreinte.total_forfait_h }} → {{ result.astreinte.total_forfait_pondere_h }} pondérés <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{% if result.astreinte.total_forfait_pondere_min %} (+ {{ result.astreinte.total_forfait_pondere_h }} de forfaits pondérés){% endif %}</span>
</div>
</div>
{% endif %}
</div>