Pointage enregistré et statistiques de présence
ci-cd / build-backend (push) Successful in 29s
ci-cd / build-frontend (push) Successful in 19s
ci-cd / deploy (push) Successful in 12s

Étape 58 : le présent / absent du référent est enregistré sur
l'affectation (Assignment.attendance + heure, migration 0030,
PUT .../assignments/{id}/attendance) au lieu de disparaître au
rechargement.

Étape 59 : section « Présence » de l'écran Statistiques — créneaux en
cours et créneaux terminés, par poste, rafraîchie toutes les 30 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AdamThomas
2026-10-28 08:06:32 +01:00
co-authored by Claude Opus 5.5
parent a37f6c2c41
commit c57c70569c
19 changed files with 600 additions and 22 deletions
+40
View File
@@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
from app.models import (
AssignmentSource,
AttendanceStatus,
BadgeAlign,
BadgeFace,
BadgeFieldType,
@@ -305,10 +306,17 @@ class AssignmentRead(ApiModel):
shift_id: uuid.UUID
source: AssignmentSource
locked: bool
attendance: AttendanceStatus | None
attendance_marked_at: dt.datetime | None
created_at: dt.datetime
updated_at: dt.datetime
class AttendanceUpdate(ApiModel):
# `None` = annuler le pointage.
attendance: AttendanceStatus | None
class AssignmentWriteResult(ApiModel):
assignment: AssignmentRead
warnings: list[Violation] = Field(default_factory=list)
@@ -758,9 +766,41 @@ class StatsNeeds(ApiModel):
volunteers_with_assignment: int
class StatsAttendanceCounts(ApiModel):
"""Pointage (`Assignment.attendance`) d'un ensemble d'affectations :
`assigned` = `present` + `absent` + `unmarked`."""
assigned: int = 0
present: int = 0
absent: int = 0
unmarked: int = 0
class StatsAttendancePosition(ApiModel):
position_id: uuid.UUID
name: str
# Créneaux terminés (`ends_at <= as_of`) et créneaux en cours.
past: StatsAttendanceCounts
live: StatsAttendanceCounts
live_shifts: int
class StatsAttendance(ApiModel):
"""Présents / absents pointés par les référents, par poste : sur les
créneaux déjà terminés et sur ceux en cours à `as_of` (heure du calcul,
locale). Les créneaux à venir ne comptent pas."""
as_of: dt.datetime
past: StatsAttendanceCounts
live: StatsAttendanceCounts
live_shifts: int
by_position: list[StatsAttendancePosition]
class EditionStats(ApiModel):
registrations: StatsRegistrations
answers: StatsAnswers
payments: StatsPayments
artist_wishes: list[StatsArtistWish]
needs: StatsNeeds
attendance: StatsAttendance