Files
benevoles/backend/app/migrations/versions/0007_drop_skills_avail_windows.py
AdamThomasandClaude Sonnet 5 be51770b51 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
2026-09-10 22:46:45 +02:00

156 lines
5.5 KiB
Python

"""Retrait des compétences ; disponibilités en fenêtres horaires libres
Revision ID: 0007_drop_skills_avail_windows
Revises: 0006_artist_and_volunteer_wishes
Create Date: 2026-09-10
- Suppression des tables `skill`, `volunteer_skill`, `shift_required_skill`.
- `availability` : on abandonne (`day_part_id`, `status`) au profit de
(`start_time`, `end_time`) — fenêtre horaire libre, plusieurs par jour.
La table est recréée (les anciennes lignes ne sont pas convertibles).
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "0007_drop_skills_avail_windows"
down_revision: str | None = "0006_artist_and_volunteer_wishes"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
_STATUS = sa.Enum(
"AVAILABLE", "IF_NEEDED", "UNAVAILABLE",
name="availabilitystatus", native_enum=False,
)
def upgrade() -> None:
op.drop_table("shift_required_skill")
op.drop_table("volunteer_skill")
op.drop_index("ix_skill_edition_id", table_name="skill")
op.drop_table("skill")
op.drop_table("availability")
op.create_table(
"availability",
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("edition_id", sa.Uuid(), nullable=False),
sa.Column("volunteer_id", sa.Uuid(), nullable=False),
sa.Column("date", sa.Date(), nullable=False),
sa.Column("start_time", sa.Time(), nullable=False),
sa.Column("end_time", sa.Time(), nullable=False),
sa.ForeignKeyConstraint(
["edition_id"], ["edition.id"], ondelete="CASCADE"
),
sa.ForeignKeyConstraint(
["volunteer_id"], ["volunteer.id"], ondelete="CASCADE"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"volunteer_id", "date", "start_time", "end_time",
name="uq_availability_window",
),
)
op.create_index(
op.f("ix_availability_edition_id"),
"availability",
["edition_id"],
unique=False,
)
op.create_index(
op.f("ix_availability_volunteer_id"),
"availability",
["volunteer_id"],
unique=False,
)
def downgrade() -> None:
op.drop_index(op.f("ix_availability_volunteer_id"), table_name="availability")
op.drop_index(op.f("ix_availability_edition_id"), table_name="availability")
op.drop_table("availability")
op.create_table(
"availability",
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("edition_id", sa.Uuid(), nullable=False),
sa.Column("volunteer_id", sa.Uuid(), nullable=False),
sa.Column("date", sa.Date(), nullable=False),
sa.Column("day_part_id", sa.Uuid(), nullable=False),
sa.Column("status", _STATUS, nullable=False),
sa.ForeignKeyConstraint(
["edition_id"], ["edition.id"], ondelete="CASCADE"
),
sa.ForeignKeyConstraint(
["volunteer_id"], ["volunteer.id"], ondelete="CASCADE"
),
sa.ForeignKeyConstraint(
["day_part_id"], ["day_part.id"], ondelete="CASCADE"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"volunteer_id", "date", "day_part_id", name="uq_availability_slot"
),
)
op.create_index(
op.f("ix_availability_edition_id"),
"availability", ["edition_id"], unique=False,
)
op.create_index(
op.f("ix_availability_volunteer_id"),
"availability", ["volunteer_id"], unique=False,
)
op.create_index(
op.f("ix_availability_day_part_id"),
"availability", ["day_part_id"], unique=False,
)
op.create_table(
"skill",
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("edition_id", sa.Uuid(), nullable=False),
sa.Column("code", sa.String(), nullable=False),
sa.Column("label", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["edition_id"], ["edition.id"], ondelete="CASCADE"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"edition_id", "code", name="uq_skill_edition_code"
),
)
op.create_index(
op.f("ix_skill_edition_id"), "skill", ["edition_id"], unique=False
)
op.create_table(
"volunteer_skill",
sa.Column("volunteer_id", sa.Uuid(), nullable=False),
sa.Column("skill_id", sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(
["skill_id"], ["skill.id"], ondelete="CASCADE"
),
sa.ForeignKeyConstraint(
["volunteer_id"], ["volunteer.id"], ondelete="CASCADE"
),
sa.PrimaryKeyConstraint("volunteer_id", "skill_id"),
)
op.create_table(
"shift_required_skill",
sa.Column("shift_id", sa.Uuid(), nullable=False),
sa.Column("skill_id", sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(
["shift_id"], ["shift.id"], ondelete="CASCADE"
),
sa.ForeignKeyConstraint(
["skill_id"], ["skill.id"], ondelete="CASCADE"
),
sa.PrimaryKeyConstraint("shift_id", "skill_id"),
)