- CRUD complet, scopé par édition, sur Position, Shift, Volunteer, Assignment
(routers app/api/*, schémas Pydantic, dépendance get_edition partagée)
- Volunteer : gestion des compétences par codes (création à la volée),
suppression = effacement complet en cascade (RGPD)
- Shift : contrôle fin > début, poste de la même édition, compétences requises
- Assignment : toute écriture active passe par le moteur de validation ;
violation bloquante -> 422 error.validation_failed, avertissements renvoyés
dans la réponse. Endpoint POST .../assignments/validate en simulation.
app/validation/loader.py construit la ValidationInput depuis la base.
- GET /editions/{id}/matrix : postes -> créneaux -> {min, ideal, confirmed,
proposed}, source de l'écran principal
- 30 tests d'API (CRUD, 404/409/422, cascade, blocage, avertissements, matrice,
loader) ; 104 tests au total ; alembic check toujours vert
- Clés de traduction shift.* / assignment.* / volunteer.* ajoutées
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
119 lines
3.0 KiB
Python
119 lines
3.0 KiB
Python
"""Fabriques d'objets du domaine pour les tests (écriture directe en base)."""
|
|
|
|
import datetime as dt
|
|
|
|
from app.models import (
|
|
Availability,
|
|
AvailabilityStatus,
|
|
Position,
|
|
PositionCategory,
|
|
Shift,
|
|
Skill,
|
|
Volunteer,
|
|
VolunteerSkill,
|
|
)
|
|
|
|
|
|
def make_position(session, edition, *, name="Bar", category=PositionCategory.BAR):
|
|
position = Position(edition_id=edition.id, name=name, category=category)
|
|
session.add(position)
|
|
session.commit()
|
|
session.refresh(position)
|
|
return position
|
|
|
|
|
|
def make_shift(
|
|
session, edition, position, *, start, end, min_staff=1, ideal_staff=2, skills=()
|
|
):
|
|
shift = Shift(
|
|
edition_id=edition.id,
|
|
position_id=position.id,
|
|
starts_at=start,
|
|
ends_at=end,
|
|
min_staff=min_staff,
|
|
ideal_staff=ideal_staff,
|
|
)
|
|
session.add(shift)
|
|
session.flush()
|
|
for code in skills:
|
|
_link_skill(session, edition, code=code, shift=shift)
|
|
session.commit()
|
|
session.refresh(shift)
|
|
return shift
|
|
|
|
|
|
def make_volunteer(
|
|
session, edition, *, first_name="Alex", last_name="Martin", email=None, skills=()
|
|
):
|
|
volunteer = Volunteer(
|
|
edition_id=edition.id,
|
|
first_name=first_name,
|
|
last_name=last_name,
|
|
email=email,
|
|
)
|
|
session.add(volunteer)
|
|
session.flush()
|
|
for code in skills:
|
|
_link_skill(session, edition, volunteer=volunteer, code=code)
|
|
session.commit()
|
|
session.refresh(volunteer)
|
|
return volunteer
|
|
|
|
|
|
def make_availability(session, edition, volunteer, day_part, *, date, status):
|
|
row = Availability(
|
|
edition_id=edition.id,
|
|
volunteer_id=volunteer.id,
|
|
day_part_id=day_part.id,
|
|
date=date,
|
|
status=status,
|
|
)
|
|
session.add(row)
|
|
session.commit()
|
|
return row
|
|
|
|
|
|
def declare_available_all_day(session, edition, volunteer, day_parts, date):
|
|
for part in day_parts.values():
|
|
make_availability(
|
|
session,
|
|
edition,
|
|
volunteer,
|
|
part,
|
|
date=date,
|
|
status=AvailabilityStatus.AVAILABLE,
|
|
)
|
|
|
|
|
|
def _get_or_create_skill(session, edition, code) -> Skill:
|
|
from sqlmodel import select
|
|
|
|
skill = session.exec(
|
|
select(Skill)
|
|
.where(Skill.edition_id == edition.id)
|
|
.where(Skill.code == code)
|
|
).first()
|
|
if skill is None:
|
|
skill = Skill(edition_id=edition.id, code=code, label=code)
|
|
session.add(skill)
|
|
session.flush()
|
|
return skill
|
|
|
|
|
|
def _link_skill(session, edition, *, code, volunteer=None, shift=None):
|
|
from app.models import ShiftRequiredSkill
|
|
|
|
skill = _get_or_create_skill(session, edition, code)
|
|
if volunteer is not None:
|
|
session.add(
|
|
VolunteerSkill(volunteer_id=volunteer.id, skill_id=skill.id)
|
|
)
|
|
if shift is not None:
|
|
session.add(
|
|
ShiftRequiredSkill(shift_id=shift.id, skill_id=skill.id)
|
|
)
|
|
|
|
|
|
def dt_at(day: int, hour: int, minute: int = 0) -> dt.datetime:
|
|
return dt.datetime(2026, 7, day, hour, minute)
|