Règles métier
Les règles de calcul des heures sont dans src/utils/history.ts. Ce sont les mêmes que sur le web : un employé doit voir les mêmes chiffres sur son ordinateur et sur son téléphone.
Les constantes
export const DAILY_TARGET_MINUTES = 7 * 60; // 420 minutes = 7 h
export const WEEKLY_TARGET_MINUTES = DAILY_TARGET_MINUTES * 5; // 2100 minutes = 35 h
const MAX_SHIFT_MINUTES = 14 * 60; // 840 minutes = 14 hTout est compté en minutes, pour éviter les arrondis. On ne convertit en heures que pour l'affichage.
Une période de travail
L'API renvoie des périodes : un début, une fin (ou null si elle est en cours), et des pauses à l'intérieur.
type WorkingTime = {
id: number;
userId: number;
start: Date;
end: Date | null;
breaks: { id: number; start: Date; end: Date | null }[];
};08:30 ─────── 12:00 ░░░░░ 12:45 ──────────── 17:15
travail pause travailLes fonctions
isForgotten(période, maintenant)
Une période qui dure plus de 14 h est un pointage oublié : la personne a oublié de pointer son départ.
export function isForgotten(period, now) {
return minutesBetween(period.start, period.end ?? now) > MAX_SHIFT_MINUTES;
}Pour une période encore ouverte, on compte jusqu'à maintenant. Une journée commencée hier à 8 h et jamais fermée devient « oubliée » à 22 h.
Une période oubliée :
- n'est pas comptée dans les totaux ;
- ne s'affiche pas comme « en service » sur l'onglet Clock ;
- apparaît comme « Départ manquant » dans l'historique.
breakMinutesOf(période, maintenant)
La somme des pauses. Une pause en cours compte jusqu'à maintenant.
workedMinutesOf(période, maintenant)
export function workedMinutesOf(period, now) {
return minutesBetween(period.start, period.end ?? now) - breakMinutesOf(period, now);
}Temps travaillé = durée de la période moins ses pauses. Pour l'exemple ci-dessus : 8 h 45 de période moins 45 min de pause = 8 h.
minutesByDay(périodes, maintenant)
Le temps travaillé par jour, pour les graphiques. Les périodes oubliées sont ignorées. Chaque période est rangée au jour où elle commence.
Le jour d'une période
Une période compte pour le jour où elle commence, même si elle se termine après minuit.
Lundi 22:00 ──────────────── Mardi 06:00
→ 8 h comptées le lundiC'est la règle BR-11 des spécifications, pour le travail de nuit.
Le statut d'un jour : describeHistory
Cette fonction transforme une liste de périodes et de corrections en une ligne par jour, avec un statut. C'est elle qui alimente l'écran Times.
type HistoryDay = {
workDate: string; // "2026-10-13"
start: Date;
end: Date | null;
breakMinutes: number;
workedMinutes: number | null;
status: StatusKind;
};Pour chaque jour (sauf aujourd'hui, qui est sur l'onglet Clock), le statut est choisi dans cet ordre :
| Statut | Quand | Ce qu'on affiche |
|---|---|---|
| Départ manquant | Une période de plus de 14 h ce jour-là, ou une demande de correction « arrivée manquante » ou « départ manquant » encore en attente | Pas de fin ni de durée : – |
| Heures supplémentaires | Le total de la semaine, en comptant ce jour, dépasse 35 h | Début, fin, durée |
| Corrigé | Une demande de correction a été approuvée pour ce jour | Début, fin, durée |
| Complet | Tout le reste | Début, fin, durée |
Les heures supplémentaires jour par jour
Les jours sont parcourus dans l'ordre, du plus ancien au plus récent, en additionnant le total de leur semaine :
| Jour | Travaillé | Total de la semaine | Statut |
|---|---|---|---|
| Lundi | 8 h | 8 h | Complet |
| Mardi | 9 h | 17 h | Complet |
| Mercredi | 9 h | 26 h | Complet |
| Jeudi | 8 h | 34 h | Complet |
| Vendredi | 7 h | 41 h | Heures supplémentaires |
Seul le vendredi est marqué : c'est ce jour-là que la semaine dépasse 35 h (règle BR-02).
Le résultat est ensuite inversé : l'écran affiche le jour le plus récent en premier.
Les absences : src/utils/absences.ts
L'API stocke une absence par jour. Pour l'affichage, groupAbsences regroupe les jours ouvrés consécutifs du même type en une période. Le week-end ne coupe pas une période : un congé du vendredi et un du lundi suivant forment une seule période.
lun. 13, mar. 14, mer. 15 (congé) → « lun. 13 oct. – mer. 15 oct. »| Constante | Valeur |
|---|---|
MAX_ABSENCE_DAYS | 31 jours au maximum pour une saisie |
Une absence ne porte jamais de motif, seulement un type : congé, maladie ou autre (règle BR-12).
Les formats : src/utils/time.ts
| Fonction | Exemple |
|---|---|
formatClockTime(date) | 08:30 |
formatMinutes(minutes) | 7 h 30 |
formatTimer(minutes) | 7h 30m 05s (le chrono sous les boutons) |
formatDay(date) | « lun. 13 oct. » dans la langue choisie |
isSameDay(a, b) | Les deux dates sont-elles le même jour ? |
isValidTime(texte) | "08:30" est-il une heure valide ? |
Ce qui n'est pas calculé par l'app
Certaines règles sont uniquement appliquées par l'API. L'app affiche simplement son refus avec un message clair :
- deux périodes d'une même personne ne se chevauchent jamais (BR-18) ;
- une semaine validée ne peut plus être modifiée (BR-13) ;
- personne ne modifie ses propres heures (BR-01) ;
- la fin d'une période ne peut pas être dans le futur.