Files
benevoles/backend/app/api/schemas.py
T
AdamThomasandClaude Sonnet 5 464635656a Étape 9 : suppression du statut d'affectation (plus de confirmation)
Une affectation est valide dès sa création ; la retirer = supprimer la ligne.

- Modèle : suppression de AssignmentStatus, ACTIVE_ASSIGNMENT_STATUSES et de
  Assignment.status ; migration 0003 (drop_column, alembic check vert)
- Loader de validation : toutes les affectations du bénévole comptent, plus de
  filtre par statut
- API : AssignmentCreate/Update/Read sans status ; création → validation
  toujours exécutée (blocage si violation bloquante) ; PATCH ne porte plus que
  source/locked
- Matrice : MatrixShift.{confirmed, proposed} → {assigned} (compte unique)
- Seed : affectations sans statut, résumé « Affectations » (260)
- Tests mis à jour (matrice, loader, modèle, API) ; 128 tests au total
- CLAUDE.md : glossaire, modèle, sémantique de validation, périmètre

Frontend : les références au statut (propositions, badges confirmé/proposé,
écran de confirmation mobile, mock, schema.ts) restent à retirer séparément.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-10 07:36:06 +02:00

236 lines
6.3 KiB
Python
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.
"""Schémas d'entrée/sortie de l'API CRUD et de la matrice."""
import datetime as dt
import uuid
from pydantic import BaseModel, ConfigDict, Field
from app.models import (
AssignmentSource,
PositionCategory,
Role,
TshirtSize,
)
from app.validation import Violation
class ApiModel(BaseModel):
"""Base commune : autorise la construction depuis un objet ORM."""
model_config = ConfigDict(from_attributes=True)
# --------------------------------------------------------------------------- #
# Volunteer #
# --------------------------------------------------------------------------- #
class VolunteerCreate(ApiModel):
first_name: str
last_name: str
email: str | None = None
phone: str | None = None
tshirt_size: TshirtSize | None = None
diet: str | None = None
comment: str | None = None
skill_codes: list[str] = Field(default_factory=list)
class VolunteerUpdate(ApiModel):
first_name: str | None = None
last_name: str | None = None
email: str | None = None
phone: str | None = None
tshirt_size: TshirtSize | None = None
diet: str | None = None
comment: str | None = None
# None = inchangé ; [] = vider les compétences.
skill_codes: list[str] | None = None
class VolunteerRead(ApiModel):
id: uuid.UUID
edition_id: uuid.UUID
first_name: str
last_name: str
email: str | None
phone: str | None
tshirt_size: TshirtSize | None
diet: str | None
comment: str | None
skill_codes: list[str]
created_at: dt.datetime
updated_at: dt.datetime
# --------------------------------------------------------------------------- #
# Position #
# --------------------------------------------------------------------------- #
class PositionCreate(ApiModel):
name: str
location: str | None = None
category: PositionCategory
lead_account_id: uuid.UUID | None = None
class PositionUpdate(ApiModel):
name: str | None = None
location: str | None = None
category: PositionCategory | None = None
lead_account_id: uuid.UUID | None = None
class PositionRead(ApiModel):
id: uuid.UUID
edition_id: uuid.UUID
name: str
location: str | None
category: PositionCategory
lead_account_id: uuid.UUID | None
created_at: dt.datetime
updated_at: dt.datetime
# --------------------------------------------------------------------------- #
# Shift #
# --------------------------------------------------------------------------- #
class ShiftCreate(ApiModel):
position_id: uuid.UUID
starts_at: dt.datetime
ends_at: dt.datetime
min_staff: int = Field(default=0, ge=0)
ideal_staff: int = Field(default=0, ge=0)
required_skill_codes: list[str] = Field(default_factory=list)
class ShiftUpdate(ApiModel):
position_id: uuid.UUID | None = None
starts_at: dt.datetime | None = None
ends_at: dt.datetime | None = None
min_staff: int | None = Field(default=None, ge=0)
ideal_staff: int | None = Field(default=None, ge=0)
required_skill_codes: list[str] | None = None
class ShiftRead(ApiModel):
id: uuid.UUID
edition_id: uuid.UUID
position_id: uuid.UUID
starts_at: dt.datetime
ends_at: dt.datetime
min_staff: int
ideal_staff: int
required_skill_codes: list[str]
created_at: dt.datetime
updated_at: dt.datetime
# --------------------------------------------------------------------------- #
# Assignment #
# --------------------------------------------------------------------------- #
class AssignmentCreate(ApiModel):
volunteer_id: uuid.UUID
shift_id: uuid.UUID
source: AssignmentSource = AssignmentSource.MANUAL
locked: bool = False
class AssignmentUpdate(ApiModel):
source: AssignmentSource | None = None
locked: bool | None = None
class AssignmentRead(ApiModel):
id: uuid.UUID
edition_id: uuid.UUID
volunteer_id: uuid.UUID
shift_id: uuid.UUID
source: AssignmentSource
locked: bool
created_at: dt.datetime
updated_at: dt.datetime
class AssignmentWriteResult(ApiModel):
assignment: AssignmentRead
warnings: list[Violation] = Field(default_factory=list)
class AssignmentValidateRequest(ApiModel):
volunteer_id: uuid.UUID
shift_id: uuid.UUID
class AssignmentValidateResult(ApiModel):
blocking: bool
violations: list[Violation] = Field(default_factory=list)
# --------------------------------------------------------------------------- #
# Matrice poste × créneau #
# --------------------------------------------------------------------------- #
class MatrixShift(ApiModel):
shift_id: uuid.UUID
starts_at: dt.datetime
ends_at: dt.datetime
min_staff: int
ideal_staff: int
assigned: int
class MatrixPosition(ApiModel):
id: uuid.UUID
name: str
category: PositionCategory
location: str | None
shifts: list[MatrixShift]
class MatrixResponse(ApiModel):
edition_id: uuid.UUID
positions: list[MatrixPosition]
# --------------------------------------------------------------------------- #
# Authentification et comptes #
# --------------------------------------------------------------------------- #
class MagicLinkRequest(ApiModel):
email: str
class VerifyRequest(ApiModel):
token: str
class LoginRequest(ApiModel):
email: str
password: str
class ChangePasswordRequest(ApiModel):
current_password: str
new_password: str
class AccountRead(ApiModel):
id: uuid.UUID
edition_id: uuid.UUID
email: str
role: Role
volunteer_id: uuid.UUID | None
# Mode de connexion : "password" (admins) ou "magic_link".
auth_method: str
created_at: dt.datetime
updated_at: dt.datetime
class TokenResponse(ApiModel):
access_token: str
token_type: str
account: AccountRead
class AccountCreate(ApiModel):
email: str
role: Role
volunteer_id: uuid.UUID | None = None
# Requis pour un compte COORDINATION, interdit sinon.
password: str | None = None