Pointage et journées
C'est le cœur du métier : comment un appui sur « Clock in » devient une journée de travail, avec ses pauses, et comment on en tire des heures.
Les trois notions
| Table | Ce que c'est | Qui l'écrit |
|---|---|---|
clocks | chaque appui : une arrivée (status: true) ou un départ (false), à une heure | la personne, en pointant |
workingtime | une journée : de l'arrivée (start) au départ (end, vide tant qu'elle continue) | le pointage, ou un manager |
breaks | les pauses d'une journée : une pause n'est pas du temps de travail | la personne, ou un manager |
Temps travaillé = durée de la journée − ses pauses.
Pointer : une arrivée ouvre la journée, un départ la ferme
Clocks.clock/2, dans une seule transaction :
- Le pointage et sa journée réussissent ou échouent ensemble : il n'existe jamais de pointage sans journée.
- Un départ pendant une pause ferme aussi la pause, à la même heure.
- La ligne de la personne est verrouillée pendant l'opération : deux appuis simultanés passent l'un après l'autre.
- Personne ne pointe pour un autre, pas même un manager (voir Droits par rôle).
Le site web envoie un simple « pointer » : l'API inverse l'état (arrivée si la personne est absente, départ sinon). L'application mobile dit ce que la personne a fait ("action": "in"), pour qu'un envoi en double ne puisse pas inverser l'état deux fois (voir Mode hors ligne).
Les règles d'une journée
| Règle | Où | Erreur |
|---|---|---|
| Une personne n'a qu'une journée en cours à la fois | index unique workingtime_one_open_per_user | 422 |
| Une personne n'a qu'une pause en cours par journée | index unique breaks_one_open_per_working_time | 422 |
| Deux journées d'une personne ne se chevauchent jamais | Workingtime.check_no_overlap | 422 overlaps |
| La fin est après le début | TimeTracking.changeset | 422 not_after_start |
| La fin n'est pas dans le futur (1 minute de tolérance) | TimeTracking.changeset | 422 in_future |
| Les pauses sont dans la journée, et ne se chevauchent pas | TimeTracking.edit_changeset | 422 outside, overlaps |
| Une semaine validée est verrouillée | WeekValidations.check_unlocked | 409 week_validated |
Pourquoi aucun chevauchement ?
Une correction corrige la journée fausse, elle n'en ajoute pas une seconde par-dessus. Sinon, les mêmes heures compteraient deux fois.
Une journée appartient au jour où elle commence
C'est la règle BR-11. Une nuit de 22:00 à 06:00 compte pour le premier jour. Une journée oubliée, fermée trois jours plus tard, compte aussi pour son jour de début.
Elle s'applique partout :
GET /api/workingtime/:userID?start=…&end=…renvoie les journées qui commencent dans la période, même si elles finissent après ;- les totaux et moyennes comptent chaque journée sur son jour de début, dans le fuseau de la mairie (
Europe/Paris) : une arrivée à 00:30 à Paris un lundi est encore dimanche en UTC, mais compte bien pour lundi.
L'oubli de départ
Une personne qui oublie de pointer son départ garde sa journée ouverte. Le pointage suivant (le lendemain matin, par exemple) la ferme : elle dure alors 20 heures, ce qui est faux.
L'API considère comme oubliée une journée de plus de 14 heures du début à la fin, pauses comprises (@max_shift_hours). La loi limite une journée à 10 heures de travail (12 par dérogation) : 14 heures est donc une limite large. Les totaux et moyennes l'ignorent, pour ne pas fausser les moyennes de l'équipe. Le site affiche alors « Missing clock-out », et l'employé la corrige par une demande de correction.
Quand un manager modifie des heures
Un manager (ou le General Manager) peut créer, modifier ou supprimer la journée de ses gens, mais jamais la sienne.
- Avec
"breaks", la modification envoie la liste complète des pauses : une pause avec unidest modifiée, une pause sansidest ajoutée, une pause absente de la liste est supprimée. - Sans
"breaks", les pauses restent comme elles sont.
Chaque écriture ajoute dans la même transaction une ligne au journal working_time_changes : l'action (create, update, delete), les heures et les pauses avant et après, et qui l'a faite. Les deux réussissent ou échouent ensemble : aucune modification n'échappe au journal.
Puis la personne reçoit une notification (dans la cloche et par e-mail). Elle retrouve toutes les modifications de ses heures dans « My Changes » (GET /api/users/:id/changes). Personne ne voit ses heures changer en silence.
Les totaux et moyennes
GET /api/users/:id/averages et GET /api/teams/:id/averages, sur une période (Reports) :
| Valeur | Calcul |
|---|---|
total_minutes | la somme du temps travaillé |
days_worked | les jours avec du travail |
per_day_minutes | total ÷ jours travaillés |
per_week_minutes | total ÷ semaines travaillées (une semaine commence le lundi) |
daily, weekly | les minutes jour par jour, semaine par semaine |
Un jour sans travail ne fait pas baisser les moyennes : un week-end n'abaisse pas la moyenne quotidienne. Seules les journées terminées et de moins de 14 heures comptent.
Le calcul est fait par PostgreSQL (Workingtime.minutes_per_day/3) : la base soustrait les pauses, convertit chaque début dans le fuseau de la mairie et additionne par jour. Les données ne remontent pas une à une dans Elixir.
Les fichiers
| Fichier | Rôle |
|---|---|
lib/timemanager/clocks.ex | pointer : arrivée, départ, rejeu |
lib/timemanager/workingtime.ex | journées, pauses, modifications et leur journal, minutes par jour |
lib/timemanager/workingtime/time_tracking.ex | les règles d'une journée et de ses pauses |
lib/timemanager/reports.ex | totaux et moyennes |