Retrait des compétences ; disponibilités en fenêtres horaires libres
Compétences supprimées de bout en bout :
- tables `skill`, `volunteer_skill`, `shift_required_skill` ;
- `Volunteer.skill_codes`, `Shift.required_skill_codes`, colonne `skills`
de l'import, règle de validation `MISSING_REQUIRED_SKILL`, jeu de démo.
Disponibilités repensées : `Availability` passe de (`day_part_id`,
`status` tri-état) à une **fenêtre horaire libre** (`date`, `start_time`,
`end_time` ; `end <= start` ⇒ passe minuit), plusieurs par jour.
- Nouveaux endpoints `GET`/`PUT /editions/{id}/volunteers/{vid}/availability`
(PUT = remplacement complet, COORDINATION) et
`GET /editions/{id}/availability` (toute l'édition, pour la grille).
- `OUTSIDE_AVAILABILITY` (toujours WARNING) : compare le créneau à l'union
des fenêtres du bénévole ce jour-là ; renvoie `date` + `uncovered_minutes`.
`DayPart` ne sert plus qu'à la borne de « journée » du cumul 8 h.
- L'import ne gère plus les disponibilités (fenêtres saisies dans l'app) :
`values.py` supprimé, mapping réduit à nom/email/téléphone/commentaire.
Migration 0007 (recrée `availability`). `AvailabilityStatus` supprimé.
158 tests, `alembic check` et `ruff` au vert. Front à suivre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
81609eecc5
commit
be51770b51
@@ -1,13 +1,10 @@
|
||||
"""Helpers partagés par les routers CRUD."""
|
||||
|
||||
import uuid
|
||||
from collections.abc import Iterable
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import delete
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.models import Skill
|
||||
from sqlmodel import Session
|
||||
|
||||
|
||||
def owned_or_404(instance, edition_id: uuid.UUID):
|
||||
@@ -17,33 +14,6 @@ def owned_or_404(instance, edition_id: uuid.UUID):
|
||||
return instance
|
||||
|
||||
|
||||
def resolve_skills(
|
||||
session: Session, edition_id: uuid.UUID, codes: Iterable[str]
|
||||
) -> list[Skill]:
|
||||
"""Récupère ou crée les compétences de l'édition pour les codes donnés."""
|
||||
existing = {
|
||||
s.code: s
|
||||
for s in session.exec(
|
||||
select(Skill).where(Skill.edition_id == edition_id)
|
||||
)
|
||||
}
|
||||
resolved: list[Skill] = []
|
||||
seen: set[str] = set()
|
||||
for raw in codes:
|
||||
code = raw.strip()
|
||||
if not code or code in seen:
|
||||
continue
|
||||
seen.add(code)
|
||||
skill = existing.get(code)
|
||||
if skill is None:
|
||||
skill = Skill(edition_id=edition_id, code=code, label=code)
|
||||
session.add(skill)
|
||||
session.flush()
|
||||
existing[code] = skill
|
||||
resolved.append(skill)
|
||||
return resolved
|
||||
|
||||
|
||||
def replace_links(
|
||||
session: Session, link_model, *, filter_column, filter_value, pairs
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Disponibilités déclarées par les bénévoles.
|
||||
|
||||
Une disponibilité = « le <date>, je peux de <start_time> à <end_time> ».
|
||||
Fenêtre horaire libre (pas liée aux créneaux types) ; plusieurs par jour
|
||||
possibles. `end_time <= start_time` ⇒ la fenêtre se termine le lendemain.
|
||||
|
||||
Ces fenêtres sont prises en compte par le moteur de validation (règle
|
||||
`OUTSIDE_AVAILABILITY`) au moment d'affecter un bénévole à un créneau.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import delete
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.api._common import owned_or_404
|
||||
from app.api.deps import get_edition
|
||||
from app.api.schemas import AvailabilityRead, AvailabilitySet
|
||||
from app.auth.deps import (
|
||||
current_account_in_edition,
|
||||
require_coordinator,
|
||||
require_staff,
|
||||
)
|
||||
from app.db import get_session
|
||||
from app.models import Account, Availability, Edition, Role, Volunteer
|
||||
|
||||
router = APIRouter(prefix="/editions/{edition_id}", tags=["availability"])
|
||||
|
||||
|
||||
def _ensure_can_read(account: Account, volunteer_id: uuid.UUID) -> None:
|
||||
if account.role in (Role.COORDINATOR, Role.LEAD):
|
||||
return
|
||||
if account.volunteer_id == volunteer_id:
|
||||
return
|
||||
raise HTTPException(status_code=403, detail={"code": "error.forbidden"})
|
||||
|
||||
|
||||
def _windows(session: Session, volunteer_id: uuid.UUID) -> list[Availability]:
|
||||
return list(
|
||||
session.exec(
|
||||
select(Availability)
|
||||
.where(Availability.volunteer_id == volunteer_id)
|
||||
.order_by(Availability.date, Availability.start_time)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/volunteers/{volunteer_id}/availability",
|
||||
response_model=list[AvailabilityRead],
|
||||
)
|
||||
def list_availability(
|
||||
volunteer_id: uuid.UUID,
|
||||
edition: Edition = Depends(get_edition),
|
||||
account: Account = Depends(current_account_in_edition),
|
||||
session: Session = Depends(get_session),
|
||||
) -> list[Availability]:
|
||||
_ensure_can_read(account, volunteer_id)
|
||||
owned_or_404(session.get(Volunteer, volunteer_id), edition.id)
|
||||
return _windows(session, volunteer_id)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/volunteers/{volunteer_id}/availability",
|
||||
response_model=list[AvailabilityRead],
|
||||
)
|
||||
def replace_availability(
|
||||
volunteer_id: uuid.UUID,
|
||||
payload: AvailabilitySet,
|
||||
edition: Edition = Depends(get_edition),
|
||||
_guard: Account = Depends(require_coordinator),
|
||||
session: Session = Depends(get_session),
|
||||
) -> list[Availability]:
|
||||
owned_or_404(session.get(Volunteer, volunteer_id), edition.id)
|
||||
|
||||
for w in payload.windows:
|
||||
if not edition.start_date <= w.date <= edition.end_date:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail={
|
||||
"code": "availability.date_outside_edition",
|
||||
"params": {"date": w.date.isoformat()},
|
||||
},
|
||||
)
|
||||
if w.start_time == w.end_time:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail={"code": "availability.empty_window"},
|
||||
)
|
||||
|
||||
session.execute(
|
||||
delete(Availability).where(Availability.volunteer_id == volunteer_id)
|
||||
)
|
||||
# Dé-doublonne (date, start, end) : la contrainte d'unicité l'imposerait.
|
||||
seen: set[tuple] = set()
|
||||
for w in payload.windows:
|
||||
key = (w.date, w.start_time, w.end_time)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
session.add(
|
||||
Availability(
|
||||
edition_id=edition.id,
|
||||
volunteer_id=volunteer_id,
|
||||
date=w.date,
|
||||
start_time=w.start_time,
|
||||
end_time=w.end_time,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
return _windows(session, volunteer_id)
|
||||
|
||||
|
||||
@router.get("/availability", response_model=list[AvailabilityRead])
|
||||
def list_edition_availability(
|
||||
edition: Edition = Depends(get_edition),
|
||||
_guard: Account = Depends(require_staff),
|
||||
session: Session = Depends(get_session),
|
||||
) -> list[Availability]:
|
||||
"""Toutes les disponibilités de l'édition (pour la grille des besoins)."""
|
||||
return list(
|
||||
session.exec(
|
||||
select(Availability)
|
||||
.where(Availability.edition_id == edition.id)
|
||||
.order_by(
|
||||
Availability.volunteer_id,
|
||||
Availability.date,
|
||||
Availability.start_time,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -94,7 +94,7 @@ def delete_day_part(
|
||||
session: Session = Depends(get_session),
|
||||
) -> Response:
|
||||
day_part = owned_or_404(session.get(DayPart, day_part_id), edition.id)
|
||||
# Cascade base : disponibilités supprimées, day_part_id des créneaux -> NULL.
|
||||
# Cascade base : day_part_id des créneaux issus de ce type repasse à NULL.
|
||||
session.delete(day_part)
|
||||
session.commit()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""Endpoints d'import : analyse puis exécution."""
|
||||
|
||||
import datetime as dt
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
||||
from pydantic import ValidationError
|
||||
from sqlmodel import Session, select
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.api.deps import get_edition
|
||||
from app.auth.deps import require_coordinator
|
||||
@@ -12,13 +10,12 @@ from app.db import get_session
|
||||
from app.importing.parser import ImportParseError, parse_upload
|
||||
from app.importing.schemas import (
|
||||
AnalyzeResponse,
|
||||
DayPartInfo,
|
||||
ImportMapping,
|
||||
ImportReport,
|
||||
PreviewRow,
|
||||
)
|
||||
from app.importing.service import run_import, suggest_mapping, validate_mapping
|
||||
from app.models import Account, DayPart, Edition
|
||||
from app.models import Account, Edition
|
||||
|
||||
router = APIRouter(prefix="/editions/{edition_id}/import", tags=["import"])
|
||||
|
||||
@@ -45,27 +42,11 @@ def _is_xlsx(filename: str, content: bytes) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _edition_dates(edition: Edition) -> list[dt.date]:
|
||||
span = (edition.end_date - edition.start_date).days
|
||||
return [edition.start_date + dt.timedelta(days=i) for i in range(span + 1)]
|
||||
|
||||
|
||||
def _day_parts(session: Session, edition: Edition) -> list[DayPart]:
|
||||
return list(
|
||||
session.exec(
|
||||
select(DayPart)
|
||||
.where(DayPart.edition_id == edition.id)
|
||||
.order_by(DayPart.order)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/analyze", response_model=AnalyzeResponse)
|
||||
async def analyze(
|
||||
file: UploadFile = File(...),
|
||||
edition: Edition = Depends(get_edition),
|
||||
_edition: Edition = Depends(get_edition),
|
||||
_guard: Account = Depends(require_coordinator),
|
||||
session: Session = Depends(get_session),
|
||||
) -> AnalyzeResponse:
|
||||
content = await _read_upload(file)
|
||||
try:
|
||||
@@ -87,17 +68,6 @@ async def analyze(
|
||||
row_count=len(table.rows),
|
||||
preview=preview,
|
||||
suggested_mapping=suggest_mapping(table.headers),
|
||||
edition_dates=_edition_dates(edition),
|
||||
day_parts=[
|
||||
DayPartInfo(
|
||||
code=dp.code,
|
||||
label=dp.label,
|
||||
start_time=dp.start_time,
|
||||
end_time=dp.end_time,
|
||||
order=dp.order,
|
||||
)
|
||||
for dp in _day_parts(session, edition)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -127,9 +97,7 @@ async def execute(
|
||||
detail={"code": exc.code, "params": exc.params},
|
||||
) from exc
|
||||
|
||||
mapping_errors = validate_mapping(
|
||||
table, parsed_mapping, edition, _day_parts(session, edition)
|
||||
)
|
||||
mapping_errors = validate_mapping(table, parsed_mapping)
|
||||
if mapping_errors:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
|
||||
@@ -49,7 +49,6 @@ class VolunteerCreate(ApiModel):
|
||||
email: str | None = None
|
||||
phone: str | None = None
|
||||
comment: str | None = None
|
||||
skill_codes: list[str] = Field(default_factory=list)
|
||||
# Vœux du bénévole : artistes à voir, postes souhaités.
|
||||
artist_ids: list[uuid.UUID] = Field(default_factory=list)
|
||||
wished_position_ids: list[uuid.UUID] = Field(default_factory=list)
|
||||
@@ -62,7 +61,6 @@ class VolunteerUpdate(ApiModel):
|
||||
phone: str | None = None
|
||||
comment: str | None = None
|
||||
# None = inchangé ; [] = vider. Idem pour artist_ids / wished_position_ids.
|
||||
skill_codes: list[str] | None = None
|
||||
artist_ids: list[uuid.UUID] | None = None
|
||||
wished_position_ids: list[uuid.UUID] | None = None
|
||||
|
||||
@@ -75,7 +73,6 @@ class VolunteerRead(ApiModel):
|
||||
email: str | None
|
||||
phone: str | None
|
||||
comment: str | None
|
||||
skill_codes: list[str]
|
||||
artist_ids: list[uuid.UUID]
|
||||
wished_position_ids: list[uuid.UUID]
|
||||
created_at: dt.datetime
|
||||
@@ -141,7 +138,6 @@ class ShiftCreate(ApiModel):
|
||||
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):
|
||||
@@ -150,7 +146,6 @@ class ShiftUpdate(ApiModel):
|
||||
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):
|
||||
@@ -162,7 +157,6 @@ class ShiftRead(ApiModel):
|
||||
ends_at: dt.datetime
|
||||
min_staff: int
|
||||
ideal_staff: int
|
||||
required_skill_codes: list[str]
|
||||
created_at: dt.datetime
|
||||
updated_at: dt.datetime
|
||||
|
||||
@@ -202,6 +196,33 @@ class DayPartUpdate(ApiModel):
|
||||
order: int | None = None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Availability (disponibilités déclarées par le bénévole) #
|
||||
# --------------------------------------------------------------------------- #
|
||||
class AvailabilityWindowIn(ApiModel):
|
||||
date: dt.date
|
||||
start_time: dt.time
|
||||
# end_time <= start_time ⇒ la fenêtre se termine le lendemain.
|
||||
end_time: dt.time
|
||||
|
||||
|
||||
class AvailabilitySet(ApiModel):
|
||||
"""Remplace l'intégralité des disponibilités d'un bénévole."""
|
||||
|
||||
windows: list[AvailabilityWindowIn] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AvailabilityRead(ApiModel):
|
||||
id: uuid.UUID
|
||||
edition_id: uuid.UUID
|
||||
volunteer_id: uuid.UUID
|
||||
date: dt.date
|
||||
start_time: dt.time
|
||||
end_time: dt.time
|
||||
created_at: dt.datetime
|
||||
updated_at: dt.datetime
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Assignment #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@@ -5,7 +5,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.api._common import owned_or_404, replace_links, resolve_skills
|
||||
from app.api._common import owned_or_404
|
||||
from app.api.deps import get_edition
|
||||
from app.api.schemas import ShiftCreate, ShiftRead, ShiftUpdate
|
||||
from app.auth.deps import (
|
||||
@@ -15,44 +15,13 @@ from app.auth.deps import (
|
||||
require_staff,
|
||||
)
|
||||
from app.db import get_session
|
||||
from app.models import (
|
||||
Account,
|
||||
Edition,
|
||||
Position,
|
||||
Shift,
|
||||
ShiftRequiredSkill,
|
||||
Skill,
|
||||
)
|
||||
from app.models import Account, Edition, Position, Shift
|
||||
|
||||
router = APIRouter(prefix="/editions/{edition_id}/shifts", tags=["shifts"])
|
||||
|
||||
|
||||
def _skill_codes(session: Session, shift_id: uuid.UUID) -> list[str]:
|
||||
return sorted(
|
||||
session.exec(
|
||||
select(Skill.code)
|
||||
.join(ShiftRequiredSkill, ShiftRequiredSkill.skill_id == Skill.id)
|
||||
.where(ShiftRequiredSkill.shift_id == shift_id)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _read(session: Session, shift: Shift) -> ShiftRead:
|
||||
return ShiftRead(
|
||||
**shift.model_dump(),
|
||||
required_skill_codes=_skill_codes(session, shift.id),
|
||||
)
|
||||
|
||||
|
||||
def _set_skills(session: Session, shift: Shift, codes: list[str]) -> None:
|
||||
skills = resolve_skills(session, shift.edition_id, codes)
|
||||
replace_links(
|
||||
session,
|
||||
ShiftRequiredSkill,
|
||||
filter_column=ShiftRequiredSkill.shift_id,
|
||||
filter_value=shift.id,
|
||||
pairs=[{"shift_id": shift.id, "skill_id": s.id} for s in skills],
|
||||
)
|
||||
def _read(shift: Shift) -> ShiftRead:
|
||||
return ShiftRead(**shift.model_dump())
|
||||
|
||||
|
||||
def _check_range(starts_at, ends_at) -> None:
|
||||
@@ -84,16 +53,11 @@ def create_shift(
|
||||
_check_range(payload.starts_at, payload.ends_at)
|
||||
position = _require_position(session, payload.position_id, edition.id)
|
||||
ensure_can_write_position(account, position)
|
||||
shift = Shift(
|
||||
edition_id=edition.id,
|
||||
**payload.model_dump(exclude={"required_skill_codes"}),
|
||||
)
|
||||
shift = Shift(edition_id=edition.id, **payload.model_dump())
|
||||
session.add(shift)
|
||||
session.flush()
|
||||
_set_skills(session, shift, payload.required_skill_codes)
|
||||
session.commit()
|
||||
session.refresh(shift)
|
||||
return _read(session, shift)
|
||||
return _read(shift)
|
||||
|
||||
|
||||
@router.get("", response_model=list[ShiftRead])
|
||||
@@ -110,7 +74,7 @@ def list_shifts(
|
||||
)
|
||||
if position_id is not None:
|
||||
stmt = stmt.where(Shift.position_id == position_id)
|
||||
return [_read(session, s) for s in session.exec(stmt)]
|
||||
return [_read(s) for s in session.exec(stmt)]
|
||||
|
||||
|
||||
@router.get("/{shift_id}", response_model=ShiftRead)
|
||||
@@ -121,7 +85,7 @@ def get_shift(
|
||||
session: Session = Depends(get_session),
|
||||
) -> ShiftRead:
|
||||
shift = owned_or_404(session.get(Shift, shift_id), edition.id)
|
||||
return _read(session, shift)
|
||||
return _read(shift)
|
||||
|
||||
|
||||
@router.patch("/{shift_id}", response_model=ShiftRead)
|
||||
@@ -135,18 +99,15 @@ def update_shift(
|
||||
shift = owned_or_404(session.get(Shift, shift_id), edition.id)
|
||||
ensure_can_write_shift(account, session, shift)
|
||||
changes = payload.model_dump(exclude_unset=True)
|
||||
skill_codes = changes.pop("required_skill_codes", None)
|
||||
if "position_id" in changes:
|
||||
target = _require_position(session, changes["position_id"], edition.id)
|
||||
ensure_can_write_position(account, target)
|
||||
for field, value in changes.items():
|
||||
setattr(shift, field, value)
|
||||
_check_range(shift.starts_at, shift.ends_at)
|
||||
if skill_codes is not None:
|
||||
_set_skills(session, shift, skill_codes)
|
||||
session.commit()
|
||||
session.refresh(shift)
|
||||
return _read(session, shift)
|
||||
return _read(shift)
|
||||
|
||||
|
||||
@router.delete("/{shift_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.api._common import owned_or_404, replace_links, resolve_skills
|
||||
from app.api._common import owned_or_404, replace_links
|
||||
from app.api.deps import get_edition
|
||||
from app.api.schemas import VolunteerCreate, VolunteerRead, VolunteerUpdate
|
||||
from app.auth.deps import (
|
||||
@@ -21,11 +21,9 @@ from app.models import (
|
||||
Edition,
|
||||
Position,
|
||||
Role,
|
||||
Skill,
|
||||
Volunteer,
|
||||
VolunteerArtist,
|
||||
VolunteerPositionWish,
|
||||
VolunteerSkill,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
@@ -41,16 +39,6 @@ def _ensure_can_read(account: Account, volunteer_id: uuid.UUID) -> None:
|
||||
raise HTTPException(status_code=403, detail={"code": "error.forbidden"})
|
||||
|
||||
|
||||
def _skill_codes(session: Session, volunteer_id: uuid.UUID) -> list[str]:
|
||||
return sorted(
|
||||
session.exec(
|
||||
select(Skill.code)
|
||||
.join(VolunteerSkill, VolunteerSkill.skill_id == Skill.id)
|
||||
.where(VolunteerSkill.volunteer_id == volunteer_id)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _artist_ids(session: Session, volunteer_id: uuid.UUID) -> list[uuid.UUID]:
|
||||
return list(
|
||||
session.exec(
|
||||
@@ -81,27 +69,11 @@ def _wished_position_ids(
|
||||
def _read(session: Session, volunteer: Volunteer) -> VolunteerRead:
|
||||
return VolunteerRead(
|
||||
**volunteer.model_dump(),
|
||||
skill_codes=_skill_codes(session, volunteer.id),
|
||||
artist_ids=_artist_ids(session, volunteer.id),
|
||||
wished_position_ids=_wished_position_ids(session, volunteer.id),
|
||||
)
|
||||
|
||||
|
||||
def _set_skills(
|
||||
session: Session, volunteer: Volunteer, codes: list[str]
|
||||
) -> None:
|
||||
skills = resolve_skills(session, volunteer.edition_id, codes)
|
||||
replace_links(
|
||||
session,
|
||||
VolunteerSkill,
|
||||
filter_column=VolunteerSkill.volunteer_id,
|
||||
filter_value=volunteer.id,
|
||||
pairs=[
|
||||
{"volunteer_id": volunteer.id, "skill_id": s.id} for s in skills
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _resolve_owned_ids(
|
||||
session: Session, model, edition_id: uuid.UUID, ids: list[uuid.UUID], code: str
|
||||
) -> list[uuid.UUID]:
|
||||
@@ -168,9 +140,7 @@ def create_volunteer(
|
||||
) -> VolunteerRead:
|
||||
volunteer = Volunteer(
|
||||
edition_id=edition.id,
|
||||
**payload.model_dump(
|
||||
exclude={"skill_codes", "artist_ids", "wished_position_ids"}
|
||||
),
|
||||
**payload.model_dump(exclude={"artist_ids", "wished_position_ids"}),
|
||||
)
|
||||
session.add(volunteer)
|
||||
try:
|
||||
@@ -180,7 +150,6 @@ def create_volunteer(
|
||||
raise HTTPException(
|
||||
status_code=409, detail={"code": "volunteer.duplicate_email"}
|
||||
) from exc
|
||||
_set_skills(session, volunteer, payload.skill_codes)
|
||||
_set_artists(session, volunteer, payload.artist_ids)
|
||||
_set_wished_positions(session, volunteer, payload.wished_position_ids)
|
||||
session.commit()
|
||||
@@ -224,7 +193,6 @@ def update_volunteer(
|
||||
) -> VolunteerRead:
|
||||
volunteer = owned_or_404(session.get(Volunteer, volunteer_id), edition.id)
|
||||
changes = payload.model_dump(exclude_unset=True)
|
||||
skill_codes = changes.pop("skill_codes", None)
|
||||
artist_ids = changes.pop("artist_ids", None)
|
||||
wished_position_ids = changes.pop("wished_position_ids", None)
|
||||
for field, value in changes.items():
|
||||
@@ -236,8 +204,6 @@ def update_volunteer(
|
||||
raise HTTPException(
|
||||
status_code=409, detail={"code": "volunteer.duplicate_email"}
|
||||
) from exc
|
||||
if skill_codes is not None:
|
||||
_set_skills(session, volunteer, skill_codes)
|
||||
if artist_ids is not None:
|
||||
_set_artists(session, volunteer, artist_ids)
|
||||
if wished_position_ids is not None:
|
||||
@@ -256,7 +222,7 @@ def delete_volunteer(
|
||||
) -> Response:
|
||||
volunteer = owned_or_404(session.get(Volunteer, volunteer_id), edition.id)
|
||||
# Effacement complet : la cascade base supprime disponibilités,
|
||||
# affectations, liens de compétence et compte éventuel.
|
||||
# affectations, vœux et compte éventuel.
|
||||
session.delete(volunteer)
|
||||
session.commit()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
Reference in New Issue
Block a user