diff --git a/CLAUDE.md b/CLAUDE.md index 26054d3..78ebf0b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -65,8 +65,8 @@ les tables sauf les tables d'association. l'écriture). - **`Volunteer`** : `email` (unique **par édition**, nullable pour les bénévoles créés à la main — plusieurs `NULL` autorisés), `first_name`, - `last_name`, `phone`, `tshirt_size`, `diet`, `comment`. Pas de date de - naissance. + `last_name`, `phone`, `comment`. Pas de date de naissance, ni de taille de + t-shirt / régime alimentaire. - **`Skill`** : référentiel par édition (`code`, `label`). N-N `Volunteer` (`volunteer_skill`). N-N `Shift` (`shift_required_skill`). - **`Position`** : `name`, `location`, `category` (`PositionCategory`), diff --git a/backend/app/api/schemas.py b/backend/app/api/schemas.py index 6c2c363..ce4cc11 100644 --- a/backend/app/api/schemas.py +++ b/backend/app/api/schemas.py @@ -9,7 +9,6 @@ from app.models import ( AssignmentSource, PositionCategory, Role, - TshirtSize, ) from app.validation import Violation @@ -49,8 +48,6 @@ class VolunteerCreate(ApiModel): 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) @@ -60,8 +57,6 @@ class VolunteerUpdate(ApiModel): 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 @@ -74,8 +69,6 @@ class VolunteerRead(ApiModel): 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 diff --git a/backend/app/i18n/locales/fr.json b/backend/app/i18n/locales/fr.json index 4ed4395..a237708 100644 --- a/backend/app/i18n/locales/fr.json +++ b/backend/app/i18n/locales/fr.json @@ -39,7 +39,6 @@ "import.row.duplicate_name_in_file": "Nom et prénom déjà présents à la ligne {first_line} du fichier.", "import.row.updated_existing": "Bénévole existant mis à jour.", "import.row.possible_duplicate_name": "Un bénévole portant le même nom existe déjà dans l'édition.", - "import.row.invalid_tshirt_size": "Taille de t-shirt non reconnue : {value}.", "import.row.skill_created": "Nouvelle compétence créée : {label}.", "import.row.unknown_availability_value": "Valeur de disponibilité non comprise « {value} » (colonne « {column} »).", diff --git a/backend/app/importing/schemas.py b/backend/app/importing/schemas.py index e9697e6..92b2257 100644 --- a/backend/app/importing/schemas.py +++ b/backend/app/importing/schemas.py @@ -19,8 +19,6 @@ class ColumnMapping(BaseModel): last_name: str email: str | None = None phone: str | None = None - tshirt_size: str | None = None - diet: str | None = None comment: str | None = None skills: str | None = None diff --git a/backend/app/importing/service.py b/backend/app/importing/service.py index 1ed4aa4..24ee615 100644 --- a/backend/app/importing/service.py +++ b/backend/app/importing/service.py @@ -12,7 +12,7 @@ from app.importing.schemas import ( RowReport, ) from app.importing.text import normalize, slug_code, split_skills -from app.importing.values import parse_availability_cell, parse_tshirt_size +from app.importing.values import parse_availability_cell from app.models import ( Availability, DayPart, @@ -29,8 +29,6 @@ _FIELD_HINTS: dict[str, tuple[str, ...]] = { "first_name": ("prenom", "first name", "firstname", "given name"), "last_name": ("nom", "last name", "lastname", "surname", "famille"), "phone": ("tel", "phone", "portable", "mobile", "gsm"), - "tshirt_size": ("tshirt", "t shirt", "taille", "shirt"), - "diet": ("regime", "alimentaire", "diet", "allergie"), "comment": ("commentaire", "remarque", "note", "precision"), "skills": ("competence", "skill", "habilitation", "brevet", "diplome"), } @@ -82,7 +80,7 @@ def validate_mapping( ) ) - for field in ("email", "phone", "tshirt_size", "diet", "comment", "skills"): + for field in ("email", "phone", "comment", "skills"): header = getattr(columns, field) if header and header not in headers: errors.append( @@ -281,25 +279,8 @@ def run_import( volunteer.email = email_raw or None if columns.phone: volunteer.phone = (row.get(columns.phone) or "").strip() or None - if columns.diet: - volunteer.diet = (row.get(columns.diet) or "").strip() or None if columns.comment: volunteer.comment = (row.get(columns.comment) or "").strip() or None - if columns.tshirt_size: - raw_size = (row.get(columns.tshirt_size) or "").strip() - if raw_size: - size = parse_tshirt_size(raw_size) - if size is None: - entry.issues.append( - Issue( - code="import.row.invalid_tshirt_size", - severity="WARNING", - field="tshirt_size", - params={"value": raw_size}, - ) - ) - else: - volunteer.tshirt_size = size session.flush() diff --git a/backend/app/importing/values.py b/backend/app/importing/values.py index faf2e76..484a0a6 100644 --- a/backend/app/importing/values.py +++ b/backend/app/importing/values.py @@ -1,7 +1,7 @@ -"""Interprétation des valeurs de cellule : disponibilités et tailles de t-shirt.""" +"""Interprétation des valeurs de cellule : disponibilités.""" from app.importing.text import normalize -from app.models import AvailabilityStatus, TshirtSize +from app.models import AvailabilityStatus _AVAILABLE = { "x", "o", "oui", "y", "yes", "ok", "1", "vrai", "true", @@ -19,18 +19,6 @@ _UNAVAILABLE = { } _EMPTY = {"", "na", "n/a", "nc"} -_TSHIRT = { - "XS": TshirtSize.XS, - "S": TshirtSize.S, - "M": TshirtSize.M, - "L": TshirtSize.L, - "XL": TshirtSize.XL, - "XXL": TshirtSize.XXL, - "2XL": TshirtSize.XXL, - "XXXL": TshirtSize.XXXL, - "3XL": TshirtSize.XXXL, -} - def parse_availability_cell(value: str) -> tuple[AvailabilityStatus | None, bool]: """(statut, reconnu). @@ -48,8 +36,3 @@ def parse_availability_cell(value: str) -> tuple[AvailabilityStatus | None, bool if norm in _UNAVAILABLE: return AvailabilityStatus.UNAVAILABLE, True return None, False - - -def parse_tshirt_size(value: str) -> TshirtSize | None: - key = normalize(value).upper().replace(" ", "").replace("-", "") - return _TSHIRT.get(key) diff --git a/backend/app/migrations/versions/0005_drop_volunteer_tshirt_diet.py b/backend/app/migrations/versions/0005_drop_volunteer_tshirt_diet.py new file mode 100644 index 0000000..8e42e87 --- /dev/null +++ b/backend/app/migrations/versions/0005_drop_volunteer_tshirt_diet.py @@ -0,0 +1,38 @@ +"""Bénévole : retrait de tshirt_size et diet + +Revision ID: 0005_drop_volunteer_tshirt_diet +Revises: 0004_shift_day_part +Create Date: 2026-09-10 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +revision: str = "0005_drop_volunteer_tshirt_diet" +down_revision: str | None = "0004_shift_day_part" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +_TSHIRT_SIZE = sa.Enum( + "XS", "S", "M", "L", "XL", "XXL", "XXXL", + name="tshirtsize", native_enum=False, +) + + +def upgrade() -> None: + op.drop_column("volunteer", "tshirt_size") + op.drop_column("volunteer", "diet") + + +def downgrade() -> None: + op.add_column( + "volunteer", sa.Column("diet", sa.String(), nullable=True) + ) + op.add_column( + "volunteer", + sa.Column("tshirt_size", _TSHIRT_SIZE, nullable=True), + ) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index bdfeeae..74e11ea 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -24,7 +24,6 @@ from app.models.enums import ( AvailabilityStatus, PositionCategory, Role, - TshirtSize, ) __all__ = [ @@ -43,7 +42,6 @@ __all__ = [ "ShiftRequiredSkill", "Skill", "TimestampMixin", - "TshirtSize", "Volunteer", "VolunteerSkill", "local_now", diff --git a/backend/app/models/domain.py b/backend/app/models/domain.py index cb00be6..99b717e 100644 --- a/backend/app/models/domain.py +++ b/backend/app/models/domain.py @@ -19,7 +19,6 @@ from app.models.enums import ( AvailabilityStatus, PositionCategory, Role, - TshirtSize, ) _PASSIVE = {"passive_deletes": True} @@ -138,11 +137,6 @@ class Volunteer(TimestampMixin, table=True): first_name: str last_name: str phone: str | None = None - tshirt_size: TshirtSize | None = Field( - default=None, - sa_column=Column(SAEnum(TshirtSize, native_enum=False), nullable=True), - ) - diet: str | None = None comment: str | None = None skills: list[Skill] = Relationship( diff --git a/backend/app/models/enums.py b/backend/app/models/enums.py index 1263765..57dda53 100644 --- a/backend/app/models/enums.py +++ b/backend/app/models/enums.py @@ -34,13 +34,3 @@ class PositionCategory(enum.StrEnum): SETUP = "SETUP" STAGE = "STAGE" OTHER = "OTHER" - - -class TshirtSize(enum.StrEnum): - XS = "XS" - S = "S" - M = "M" - L = "L" - XL = "XL" - XXL = "XXL" - XXXL = "XXXL" diff --git a/backend/app/seed.py b/backend/app/seed.py index c1838cc..7f8c7d2 100644 --- a/backend/app/seed.py +++ b/backend/app/seed.py @@ -30,7 +30,6 @@ from app.models import ( Shift, ShiftRequiredSkill, Skill, - TshirtSize, Volunteer, VolunteerSkill, ) @@ -354,12 +353,6 @@ def build(session: Session, *, rng: random.Random | None = None) -> Summary: last_name=last, email=email, phone=(f"06{rng.randint(10_000_000, 99_999_999)}" if rng.random() < 0.8 else None), - tshirt_size=(rng.choice(list(TshirtSize)) if rng.random() < 0.9 else None), - diet=( - rng.choice(["Végétarien", "Végétalien", "Sans gluten", "Sans porc"]) - if rng.random() < 0.25 - else None - ), ) session.add(volunteer) session.flush() diff --git a/backend/tests/test_api_volunteers.py b/backend/tests/test_api_volunteers.py index 0dc06e1..70179c1 100644 --- a/backend/tests/test_api_volunteers.py +++ b/backend/tests/test_api_volunteers.py @@ -23,14 +23,12 @@ def test_create_volunteer_with_skills(client, edition): "first_name": "Alex", "last_name": "Martin", "email": "alex@ex.fr", - "tshirt_size": "M", "skill_codes": ["SST", "PERMIS_B", "SST"], }, ) assert resp.status_code == 201 body = resp.json() assert body["skill_codes"] == ["PERMIS_B", "SST"] - assert body["tshirt_size"] == "M" assert body["edition_id"] == str(edition.id) diff --git a/backend/tests/test_import_service.py b/backend/tests/test_import_service.py index 8239632..5dac763 100644 --- a/backend/tests/test_import_service.py +++ b/backend/tests/test_import_service.py @@ -21,7 +21,6 @@ def _mapping(dry_run: bool = False) -> ImportMapping: first_name="Prénom", last_name="Nom", email="Email", - tshirt_size="Taille", skills="Compétences", ), availability_columns=[ @@ -78,7 +77,6 @@ def test_happy_path_creates_volunteers_skills_and_availabilities(session, editio volunteers = {v.email: v for v in _volunteers(session, edition)} assert set(volunteers) == {"alex@ex.fr", "sam@ex.fr"} - assert volunteers["alex@ex.fr"].tshirt_size.value == "M" assert {s.code for s in volunteers["alex@ex.fr"].skills} == {"SST", "PERMIS_B"} avails = session.exec( @@ -194,18 +192,6 @@ def test_invalid_email_is_skipped(session, edition): assert report.rows[0].issues[0].code == "import.row.invalid_email" -def test_unknown_tshirt_size_warns_and_still_imports(session, edition): - report = run_import( - session=session, - edition=edition, - table=_table([["Alex", "Martin", "a@ex.fr", "Large", "", "x", ""]]), - mapping=_mapping(), - ) - assert report.created == 1 - assert report.rows[0].issues[0].code == "import.row.invalid_tshirt_size" - assert _volunteers(session, edition)[0].tshirt_size is None - - def test_unknown_availability_value_is_flagged(session, edition): report = run_import( session=session, diff --git a/backend/tests/test_import_values.py b/backend/tests/test_import_values.py index 6168ebe..9ebe121 100644 --- a/backend/tests/test_import_values.py +++ b/backend/tests/test_import_values.py @@ -1,8 +1,8 @@ import pytest from app.importing.text import slug_code, split_skills -from app.importing.values import parse_availability_cell, parse_tshirt_size -from app.models import AvailabilityStatus, TshirtSize +from app.importing.values import parse_availability_cell +from app.models import AvailabilityStatus @pytest.mark.parametrize( @@ -32,24 +32,6 @@ def test_unknown_cell_is_flagged(): assert parse_availability_cell("bof") == (None, False) -@pytest.mark.parametrize( - ("raw", "expected"), - [ - ("M", TshirtSize.M), - (" m ", TshirtSize.M), - ("xl", TshirtSize.XL), - ("2XL", TshirtSize.XXL), - ("3 XL", TshirtSize.XXXL), - ], -) -def test_tshirt_sizes(raw, expected): - assert parse_tshirt_size(raw) is expected - - -def test_unknown_tshirt_size_is_none(): - assert parse_tshirt_size("large") is None - - def test_slug_code_is_stable_across_accents_and_case(): assert slug_code("Régie son") == "REGIE_SON" assert slug_code(" permis B ") == "PERMIS_B"