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:
@ -12,11 +12,12 @@
|
||||
(j.aprem_entree and j.aprem_entree > borne_aprem) or
|
||||
(j.aprem_sortie and j.aprem_sortie < plages.aprem_fin)
|
||||
) %}
|
||||
<tr id="row-{{ j.date }}"{% if nom in ['Samedi', 'Dimanche'] %} class="we"{% endif %}>
|
||||
<tr id="row-{{ j.date }}"{% if nom in ['Samedi', 'Dimanche'] or j.ferie %} class="we"{% endif %}>
|
||||
|
||||
<td class="col-jour">
|
||||
<span class="jour-nom">{{ nom }}</span>
|
||||
<span class="jour-date">{{ j.date[8:] }}/{{ j.date[5:7] }}</span>
|
||||
{% if j.ferie %}<span class="ferie-badge" title="Jour férié : dû 0, astreinte comptée 0h00 → 0h00">férié</span>{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="c-matin">
|
||||
@ -106,6 +107,10 @@
|
||||
hx-swap="none"
|
||||
class="btn-cg btn-jour {% if 'jour' in j.conge %}jour-on{% endif %}"
|
||||
title="Journée entière">J</button>
|
||||
<button hx-post="/ferie/{{ j.date }}/toggle"
|
||||
hx-swap="none"
|
||||
class="btn-cg btn-ferie {% if j.ferie %}ferie-on{% endif %}"
|
||||
title="Jour férié : dû 0, astreinte comptée 0h00 → 0h00">F</button>
|
||||
<button hx-post="/astreinte/{{ j.date }}/toggle"
|
||||
hx-swap="none"
|
||||
class="btn-cg btn-as {% if j.astreinte %}as-on{% endif %}"
|
||||
@ -127,7 +132,7 @@
|
||||
</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.astreinte_min %}<span class="as-total" title="Compteur séparé, hors heures de semaine">astreinte : {{ j.astreinte_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">
|
||||
<div class="as-grid" id="as-events-{{ j.date }}">
|
||||
|
||||
@ -45,7 +45,13 @@
|
||||
{% endif %}
|
||||
{% if result.astreinte and result.astreinte.actif %}
|
||||
<div class="as-decompte">
|
||||
<div class="as-decompte-title">Astreinte — tranches de 15 min entamées, comptées entières (compteur séparé, hors heures de semaine)</div>
|
||||
<div class="as-decompte-title">Astreinte — compteur séparé, hors heures de semaine</div>
|
||||
<div class="as-decompte-help">
|
||||
Décompte par <b>quarts d'heure de l'horloge</b> (:00, :15, :30, :45) : tout quart
|
||||
<b>entamé</b> compte 15 min entières, et un quart partagé par deux
|
||||
interventions n'est compté qu'<b>une seule fois</b>. Ex. 22:39 → 23:49 =
|
||||
1h10 réelles mais 6 quarts touchés (22:30 → 00:00) = 1h30.
|
||||
</div>
|
||||
{% for j in result.astreinte.jours %}
|
||||
<div class="as-jour">
|
||||
<span class="as-jour-date">{{ j.date[8:] }}/{{ j.date[5:7] }}</span>
|
||||
@ -53,9 +59,13 @@
|
||||
<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 %}
|
||||
<span class="as-ev-q">{{ ev.tranches }} tr. = {{ ev.min_h }}</span>
|
||||
{% if ev.tranches %}
|
||||
<span class="as-ev-q" title="Quarts d'heure de l'horloge (:00, :15, :30, :45) touchés par l'intervention et pas déjà comptés, chacun compté 15 min entières">{{ ev.tranches }} quart{{ 's' if ev.tranches > 1 else '' }} ({{ ev.tranches_debut }} → {{ ev.tranches_fin }}) = {{ ev.min_h }}</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="Tous les quarts touchés sont déjà comptés dans une intervention précédente (même quart d'heure)">0 quart — déjà compté</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@ -436,6 +436,20 @@
|
||||
.btn-cg.jour-on { background: var(--accent-2); border-color: #955505; color: #fff; }
|
||||
.btn-cg.btn-as { border-radius: 4px; }
|
||||
.btn-cg.as-on { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||
.btn-cg.btn-ferie { border-radius: 4px; }
|
||||
.btn-cg.ferie-on { background: #7B5EA7; border-color: #7B5EA7; color: #fff; }
|
||||
.ferie-badge {
|
||||
display: inline-block;
|
||||
font-family: var(--mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color: #7B5EA7;
|
||||
border: 1px solid #7B5EA7;
|
||||
border-radius: 3px;
|
||||
padding: 0 .25rem;
|
||||
margin-left: .3rem;
|
||||
}
|
||||
|
||||
/* Astreinte : sous-ligne pleine largeur sous la ligne du jour (hors colonne
|
||||
après-midi), heures d'intervention (début/fin/description) repliées dans un
|
||||
@ -559,10 +573,22 @@
|
||||
color: var(--muted);
|
||||
margin-bottom: .35rem;
|
||||
}
|
||||
.as-decompte-help {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
margin: -.15rem 0 .45rem;
|
||||
}
|
||||
.as-decompte-help b { color: var(--text); font-weight: 500; }
|
||||
.as-ev { display: flex; flex-wrap: wrap; gap: .45rem; align-items: baseline; padding: .12rem 0; }
|
||||
.as-ev-h { font-family: var(--mono); }
|
||||
.as-ev-desc { color: var(--muted); font-style: italic; }
|
||||
.as-ev-q { font-family: var(--mono); color: var(--text); }
|
||||
.as-ev-q {
|
||||
font-family: var(--mono);
|
||||
color: var(--text);
|
||||
cursor: help;
|
||||
border-bottom: 1px dotted var(--rule);
|
||||
}
|
||||
.as-ev-cat, .as-cat { color: var(--accent); font-family: var(--mono); font-size: 11px; }
|
||||
.as-ev-pond { font-family: var(--mono); color: var(--accent-2); }
|
||||
.as-jour-date { font-family: var(--mono); font-weight: 500; margin-right: .4rem; }
|
||||
|
||||
@ -54,24 +54,74 @@
|
||||
<span class="hist-label">Ratios des heures d'astreinte</span>
|
||||
<p class="settings-p">
|
||||
Pondération des heures d'astreinte selon le type de jour et la fenêtre horaire :
|
||||
le décompte se fait par tranches de 15 min (toute tranche entamée compte en
|
||||
totalité) et chaque tranche est multipliée par le ratio de la première règle qui
|
||||
la couvre. Une tranche hors règle compte ×1 (« Autre »). Règles globales à tous
|
||||
les comptes, ajustables dans <code>data/config.json</code> (clé
|
||||
<code>astreinte_facteurs</code>).
|
||||
le décompte se fait par quarts d'heure de l'horloge (:00, :15, :30, :45) — tout
|
||||
quart entamé compte 15 min entières (ex. 22:39 → 23:49 touche les quarts 22:30 à
|
||||
23:45, soit 6 quarts = 1h30) — puis chaque quart est multiplié par le ratio de la
|
||||
première règle qui le couvre. Un quart hors règle compte ×1 (« Autre ») ; la
|
||||
première règle qui couvre un quart gagne, place les fenêtres spécifiques avant
|
||||
les générales. Samedi, dimanche et jours fériés comptent de 0h00 à 0h00. Règles
|
||||
globales à tous les comptes.
|
||||
</p>
|
||||
<div class="as-ratios">
|
||||
{% for f in facteurs %}
|
||||
<div class="as-ratio-row">
|
||||
<span class="as-ratio-nom">{{ f.nom }}</span>
|
||||
<span class="as-ratio-jours">{% for j in f.jours %}{{ {'semaine': 'Lun–Ven', 'sam': 'Samedi', 'dim': 'Dimanche', 'ferie': 'Férié'}[j] }}{% if not loop.last %} · {% endif %}{% endfor %}</span>
|
||||
<span class="as-ratio-fenetre">{{ f.debut }} → {{ f.fin }}</span>
|
||||
<span class="as-ratio-facteur">×{{ '%g' % f.facteur }}</span>
|
||||
|
||||
{% if facteurs_saved %}
|
||||
<div class="settings-saved">Enregistré.</div>
|
||||
{% endif %}
|
||||
{% if facteurs_error %}
|
||||
<div class="settings-error">Règles invalides : chaque règle doit avoir un nom, au moins un type de jour, une fenêtre HH:MM et un ratio > 0 ; au moins une règle est requise (efface le nom pour supprimer une règle).</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/settings/astreinte-facteurs" class="as-facteurs-form">
|
||||
<div id="as-ratio-rows">
|
||||
{% for f in facteurs %}
|
||||
{% set i = loop.index0 %}
|
||||
<div class="as-ratio-edit" data-idx="{{ i }}">
|
||||
<input type="text" class="login-input as-f-nom" name="f_nom_{{ i }}" value="{{ f.nom }}" maxlength="40" placeholder="Nom de la règle">
|
||||
<div class="as-f-jours">
|
||||
{% for j, lbl in [('semaine', 'Lun–Ven'), ('sam', 'Sam'), ('dim', 'Dim'), ('ferie', 'Férié')] %}
|
||||
<label class="as-f-jour" title="Types de jours couverts par la règle"><input type="checkbox" name="f_jours_{{ i }}" value="{{ j }}" {% if j in f.jours %}checked{% endif %}> {{ lbl }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input class="login-input time-range-input as-f-h" type="time" name="f_debut_{{ i }}" value="{{ f.debut }}" aria-label="Début de fenêtre">
|
||||
<span class="settings-range-sep">→</span>
|
||||
<input class="login-input time-range-input as-f-h" type="time" name="f_fin_{{ i }}" value="{{ f.fin }}" aria-label="Fin de fenêtre (avant début = à cheval sur minuit)">
|
||||
<label class="as-f-x">×<input class="login-input as-f-facteur" type="number" name="f_facteur_{{ i }}" value="{{ '%g' % f.facteur }}" step="0.05" min="0.05" aria-label="Ratio"></label>
|
||||
<button type="button" class="as-f-del" title="Supprimer la règle" onclick="asDelRatio(this)">×</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="as-ratio-actions">
|
||||
<button type="button" class="as-f-add" onclick="asAddRatio()">+ règle</button>
|
||||
<button class="login-btn" type="submit">Enregistrer les ratios</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function asAddRatio() {
|
||||
const rows = document.getElementById('as-ratio-rows');
|
||||
const idx = Math.max(-1, ...[...rows.querySelectorAll('.as-ratio-edit')].map(r => +r.dataset.idx)) + 1;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'as-ratio-edit';
|
||||
div.dataset.idx = idx;
|
||||
div.innerHTML =
|
||||
'<input type="text" class="login-input as-f-nom" name="f_nom_' + idx + '" maxlength="40" placeholder="Nom de la règle">' +
|
||||
'<div class="as-f-jours">' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="semaine" checked> Lun–Ven</label>' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="sam"> Sam</label>' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="dim"> Dim</label>' +
|
||||
'<label class="as-f-jour"><input type="checkbox" name="f_jours_' + idx + '" value="ferie"> Férié</label>' +
|
||||
'</div>' +
|
||||
'<input class="login-input time-range-input as-f-h" type="time" name="f_debut_' + idx + '" value="20:00">' +
|
||||
'<span class="settings-range-sep">→</span>' +
|
||||
'<input class="login-input time-range-input as-f-h" type="time" name="f_fin_' + idx + '" value="08:00">' +
|
||||
'<label class="as-f-x">×<input class="login-input as-f-facteur" type="number" name="f_facteur_' + idx + '" value="1.5" step="0.05" min="0.05"></label>' +
|
||||
'<button type="button" class="as-f-del" title="Supprimer la règle" onclick="asDelRatio(this)">×</button>';
|
||||
rows.appendChild(div);
|
||||
div.querySelector('.as-f-nom').focus();
|
||||
}
|
||||
function asDelRatio(btn) { btn.closest('.as-ratio-edit').remove(); }
|
||||
</script>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">Forfait par jour d'astreinte</span>
|
||||
<p class="settings-p">
|
||||
@ -226,22 +276,47 @@
|
||||
border: 1px solid var(--rule-l);
|
||||
padding: 0 .25rem;
|
||||
}
|
||||
.as-ratios { border: 1px solid var(--rule-l); display: flex; flex-direction: column; }
|
||||
.as-ratio-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(110px, auto) 1fr auto auto;
|
||||
gap: .5rem 1rem;
|
||||
align-items: baseline;
|
||||
padding: .45rem .8rem;
|
||||
.as-facteurs-form { display: flex; flex-direction: column; gap: .5rem; }
|
||||
.as-ratio-edit {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: .5rem .75rem;
|
||||
padding: .5rem .8rem;
|
||||
border: 1px solid var(--rule-l);
|
||||
}
|
||||
.as-ratio-row + .as-ratio-row { border-top: 1px solid var(--rule-l); }
|
||||
.as-ratio-nom { font-size: 13px; font-weight: 500; }
|
||||
.as-ratio-jours { font-size: 12px; color: var(--muted); }
|
||||
.as-ratio-fenetre { font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
||||
.as-ratio-facteur { font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--accent-2); }
|
||||
.as-ratio-edit + .as-ratio-edit { margin-top: .4rem; }
|
||||
.as-ratio-edit .as-f-nom { width: 140px; flex-shrink: 0; }
|
||||
.as-f-jours { display: flex; gap: .75rem; flex-wrap: wrap; flex: 1; }
|
||||
.as-f-jour { font-size: 12px; color: var(--text); display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap; }
|
||||
.as-ratio-edit .as-f-h { width: 90px; flex-shrink: 0; }
|
||||
.as-f-x { font-family: var(--mono); color: var(--muted); display: inline-flex; align-items: center; gap: .3rem; }
|
||||
.as-ratio-edit .as-f-facteur { width: 70px; }
|
||||
.as-f-del {
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--surface);
|
||||
color: var(--muted);
|
||||
border-radius: 4px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.as-f-del:hover { color: var(--neg); border-color: var(--neg); }
|
||||
.as-ratio-actions { display: flex; gap: .75rem; align-items: center; margin-top: .3rem; }
|
||||
.as-f-add {
|
||||
border: 1px dashed var(--rule);
|
||||
background: none;
|
||||
color: var(--muted);
|
||||
border-radius: 4px;
|
||||
padding: .35rem .7rem;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.as-f-add:hover { color: var(--accent); border-color: var(--accent); }
|
||||
@media (max-width: 600px) {
|
||||
.as-ratio-row { grid-template-columns: 1fr auto; row-gap: .1rem; }
|
||||
.as-ratio-jours, .as-ratio-fenetre { grid-column: 1 / -1; }
|
||||
.as-f-jours { order: 3; width: 100%; }
|
||||
}
|
||||
.settings-form { display: flex; flex-direction: column; gap: .5rem; max-width: 420px; }
|
||||
.settings-saved {
|
||||
|
||||
Reference in New Issue
Block a user