Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
640f741
feat: adding consent fields to model
maximeroucher Aug 24, 2026
8c0d894
feat(migration): adding column do db
maximeroucher Aug 24, 2026
3e0b568
feat: adding consent to schema
maximeroucher Aug 24, 2026
1f48909
feat: excluding security file when pulling all participants
maximeroucher Aug 24, 2026
6337111
feat: adding consent when creating security file
maximeroucher Aug 24, 2026
4f113ea
feat: gating security file update based on consent only
maximeroucher Aug 24, 2026
accc9f3
feat: adding medical read permission and logging
maximeroucher Aug 24, 2026
ae08355
feat: removing security file on admin access of a participant
maximeroucher Aug 24, 2026
18c61cd
feat: adding course responsible as emergency contact
maximeroucher Aug 24, 2026
43a62d1
feat: generating security file with course responsible data
maximeroucher Aug 24, 2026
35997e2
feat: updating testing setup with mocked course responsible
maximeroucher Aug 24, 2026
d256ba8
feat: adding consent default data for test
maximeroucher Aug 24, 2026
85bff35
feat: renaming is admin to is raid admin
maximeroucher Aug 24, 2026
acaeaf0
feat: gating security data read via selecting load restriction
maximeroucher Aug 24, 2026
96f1970
fix: nullable consent_given_at in migration
armanddidierjean Aug 24, 2026
4524507
fix: remove print
armanddidierjean Aug 24, 2026
e7d5ede
fix: don't copy list before selecting relationship in cruds
armanddidierjean Aug 24, 2026
a37812e
fix: select relationship
armanddidierjean Aug 24, 2026
4f8a265
feat: use restricted participant without securityfile when possible
armanddidierjean Aug 25, 2026
91b3a76
Return progress for GET admin participant
armanddidierjean Aug 25, 2026
ea66be7
feat: adding own team complete retrieval for participant
maximeroucher Aug 27, 2026
38c3e3a
Select only the team once when getting complete team
armanddidierjean Aug 31, 2026
72d3037
Add test
armanddidierjean Aug 31, 2026
f81c3a9
fix: inherit RaidParticipantRestrictedComplete from RaidParticipantRe…
armanddidierjean Sep 2, 2026
d1f37a7
fix: include RaidParticipantRestrictedComplete in RaidTeamComplete
armanddidierjean Sep 2, 2026
c8760cd
feat: adding raid volunteer payment
maximeroucher Sep 4, 2026
b1c2a13
fix: rebase migration
armanddidierjean Sep 4, 2026
b110753
feat: adding scholarship price
maximeroucher Sep 10, 2026
b332b73
feat: adding document type
maximeroucher Sep 10, 2026
5cbcbfb
feat: adding scholarship status and document to model
maximeroucher Sep 10, 2026
985fa21
feat: adding scholarship price migration
maximeroucher Sep 11, 2026
16b9481
feat: adding school authorization to queryable documents
maximeroucher Sep 11, 2026
8966fbd
feat: adding check in validation checker
maximeroucher Sep 10, 2026
6b7e1c4
feat: updating participant price calculation
maximeroucher Sep 10, 2026
6cbfc0d
feat: cleaning casting
maximeroucher Sep 10, 2026
b6517d1
feat: updating tests
maximeroucher Sep 11, 2026
bd32789
Merge branch 'main' into raidV2-improvments
barimuls Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/modules/raid/coredata_raid.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class RaidInformation(core_data.BaseCoreData):
president: EmergencyContact | None = None
volunteer_responsible: EmergencyContact | None = None
security_responsible: EmergencyContact | None = None
course_responsible: EmergencyContact | None = None
Comment thread
Marc-Andrieu marked this conversation as resolved.
rescue: EmergencyContact | None = None
raid_rules_id: str | None = None
raid_information_id: str | None = None
Expand All @@ -22,4 +23,6 @@ class RaidPrice(core_data.BaseCoreData):
student_price: int | None = None
partner_price: int | None = None
external_price: int | None = None
scholarship_price: int | None = None
t_shirt_price: int | None = None
volunteer_price: int | None = None
252 changes: 220 additions & 32 deletions app/modules/raid/cruds_raid.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,32 @@
RaidRegistrationStatus,
)

PARTICIPANT_DATA_TO_SELECT = [
models_raid.RaidParticipant.id_card,
models_raid.RaidParticipant.medical_certificate,
models_raid.RaidParticipant.student_card,
models_raid.RaidParticipant.raid_rules,
models_raid.RaidParticipant.parent_authorization,
models_raid.RaidParticipant.school_authorization,
models_raid.RaidParticipant.user,
]

TEAM_DATA_TO_SELECT = [
selectinload(models_raid.RaidTeam.captain).selectinload(attribute)
for attribute in PARTICIPANT_DATA_TO_SELECT
] + [
selectinload(models_raid.RaidTeam.second).selectinload(attribute)
for attribute in PARTICIPANT_DATA_TO_SELECT
]


async def create_participant(
participant: schemas_raid.RaidParticipantCreate,
db: AsyncSession,
) -> None:
db.add(
models_raid.RaidParticipant(
user_id=participant.user_id,
edition_id=participant.edition_id,
status=participant.status,
address=participant.address,
bike_size=participant.bike_size,
t_shirt_size=participant.t_shirt_size,
situation=participant.situation,
other_school=participant.other_school,
company=participant.company,
diet=participant.diet,
id_card_id=participant.id_card_id,
medical_certificate_id=participant.medical_certificate_id,
security_file_id=participant.security_file_id,
student_card_id=participant.student_card_id,
raid_rules_id=participant.raid_rules_id,
parent_authorization_id=participant.parent_authorization_id,
attestation_on_honour=participant.attestation_on_honour,
payment=participant.payment,
t_shirt_payment=participant.t_shirt_payment,
is_minor=participant.is_minor,
**participant.model_dump(),
),
)
await db.flush()
Expand All @@ -49,18 +48,24 @@ async def get_all_participants(
edition_id: UUID,
db: AsyncSession,
status: RaidRegistrationStatus | None = None,
) -> list[schemas_raid.RaidParticipant]:
) -> list[schemas_raid.RaidParticipantRestricted]:
stmt = (
select(models_raid.RaidParticipant)
.where(models_raid.RaidParticipant.edition_id == edition_id)
.options(selectinload("*"))
.options(
*[selectinload(data) for data in PARTICIPANT_DATA_TO_SELECT],
)
)
if status is not None:
stmt = stmt.where(models_raid.RaidParticipant.status == status)
participants = await db.execute(stmt)

# Remove security_file from the participants list to avoid including it in the response.
found_participants = participants.scalars().all()

Comment thread
maximeroucher marked this conversation as resolved.
return [
schemas_raid.RaidParticipant.model_validate(p)
for p in participants.scalars().all()
schemas_raid.RaidParticipantRestricted.model_validate(participant)
for participant in found_participants
]


Expand Down Expand Up @@ -146,24 +151,83 @@ async def get_team_by_participant_id(
models_raid.RaidTeam.second_id == user_id,
),
)
.options(selectinload("*")),
.options(
*TEAM_DATA_TO_SELECT,
),
)
model = team.scalars().first()
return schemas_raid.RaidTeam.model_validate(model) if model else None


async def get_team_including_security_files_by_participant_id(
user_id: str,
edition_id: UUID,
db: AsyncSession,
) -> schemas_raid.RaidTeamIncludingSecurityFile | None:
team = await db.execute(
select(models_raid.RaidTeam)
.where(
models_raid.RaidTeam.edition_id == edition_id,
or_(
models_raid.RaidTeam.captain_id == user_id,
models_raid.RaidTeam.second_id == user_id,
),
)
.options(
*TEAM_DATA_TO_SELECT,
selectinload(models_raid.RaidTeam.captain).selectinload(
models_raid.RaidParticipant.security_file,
),
selectinload(models_raid.RaidTeam.second).selectinload(
models_raid.RaidParticipant.security_file,
),
),
)
model = team.scalars().first()
return (
schemas_raid.RaidTeamIncludingSecurityFile.model_validate(model)
if model
else None
)


async def get_all_teams(
edition_id: UUID,
db: AsyncSession,
) -> list[schemas_raid.RaidTeam]:
teams = await db.execute(
select(models_raid.RaidTeam)
.where(models_raid.RaidTeam.edition_id == edition_id)
.options(selectinload("*")),
.options(
*TEAM_DATA_TO_SELECT,
),
)
return [schemas_raid.RaidTeam.model_validate(t) for t in teams.scalars().all()]


async def get_all_teams_including_security_files(
edition_id: UUID,
db: AsyncSession,
) -> list[schemas_raid.RaidTeamIncludingSecurityFile]:
teams = await db.execute(
select(models_raid.RaidTeam)
.where(models_raid.RaidTeam.edition_id == edition_id)
.options(
*TEAM_DATA_TO_SELECT,
selectinload(models_raid.RaidTeam.captain).selectinload(
models_raid.RaidParticipant.security_file,
),
selectinload(models_raid.RaidTeam.second).selectinload(
models_raid.RaidParticipant.security_file,
),
),
)
return [
schemas_raid.RaidTeamIncludingSecurityFile.model_validate(t)
for t in teams.scalars().all()
]


async def get_all_validated_teams(
edition_id: UUID,
db: AsyncSession,
Expand Down Expand Up @@ -196,7 +260,9 @@ async def get_all_validated_teams(
Captain.c.status == RaidRegistrationStatus.validated,
Second.c.status == RaidRegistrationStatus.validated,
)
.options(selectinload("*"))
.options(
*TEAM_DATA_TO_SELECT,
)
)
teams = await db.execute(stmt)
return [schemas_raid.RaidTeam.model_validate(t) for t in teams.scalars().all()]
Expand All @@ -209,12 +275,39 @@ async def get_team_by_id(
team = await db.execute(
select(models_raid.RaidTeam)
.where(models_raid.RaidTeam.id == team_id)
.options(selectinload("*")),
.options(
*TEAM_DATA_TO_SELECT,
),
)
model = team.scalars().first()
return schemas_raid.RaidTeam.model_validate(model) if model else None


async def get_team_including_security_file_by_id(
team_id: str,
db: AsyncSession,
) -> schemas_raid.RaidTeamIncludingSecurityFile | None:
team = await db.execute(
select(models_raid.RaidTeam)
.where(models_raid.RaidTeam.id == team_id)
.options(
*TEAM_DATA_TO_SELECT,
selectinload(models_raid.RaidTeam.captain).selectinload(
models_raid.RaidParticipant.security_file,
),
selectinload(models_raid.RaidTeam.second).selectinload(
models_raid.RaidParticipant.security_file,
),
),
)
model = team.scalars().first()
return (
schemas_raid.RaidTeamIncludingSecurityFile.model_validate(model)
if model
else None
)


async def create_team(
team: schemas_raid.RaidTeamCreate,
db: AsyncSession,
Expand Down Expand Up @@ -372,6 +465,8 @@ async def add_security_file(
emergency_person_name=security_file.emergency_person_name,
emergency_person_phone=security_file.emergency_person_phone,
file_id=security_file.file_id,
consent_given=security_file.consent_given,
consent_given_at=security_file.consent_given_at,
),
)
await db.flush()
Expand Down Expand Up @@ -495,7 +590,7 @@ async def get_document_by_id(
async def get_user_by_document_id(
document_id: str,
db: AsyncSession,
) -> schemas_raid.RaidParticipant | None:
) -> schemas_raid.RaidParticipantRestricted | None:
document = await db.execute(
select(models_raid.RaidParticipant)
.where(
Expand All @@ -505,12 +600,17 @@ async def get_user_by_document_id(
models_raid.RaidParticipant.student_card_id == document_id,
models_raid.RaidParticipant.raid_rules_id == document_id,
models_raid.RaidParticipant.parent_authorization_id == document_id,
models_raid.RaidParticipant.school_authorization_id == document_id,
),
)
.options(selectinload("*")),
.options(
*[selectinload(data) for data in PARTICIPANT_DATA_TO_SELECT],
),
)
model = document.scalars().first()
return schemas_raid.RaidParticipant.model_validate(model) if model else None
return (
schemas_raid.RaidParticipantRestricted.model_validate(model) if model else None
)


async def update_document(
Expand Down Expand Up @@ -570,6 +670,38 @@ async def confirm_t_shirt_payment(
await db.flush()


async def confirm_volunteer_payment(
user_id: str,
edition_id: UUID,
db: AsyncSession,
) -> None:
await db.execute(
update(models_raid.RaidVolunteer)
.where(
models_raid.RaidVolunteer.user_id == user_id,
models_raid.RaidVolunteer.edition_id == edition_id,
)
.values(payment=True),
)
await db.flush()


async def confirm_volunteer_t_shirt_payment(
user_id: str,
edition_id: UUID,
db: AsyncSession,
) -> None:
await db.execute(
update(models_raid.RaidVolunteer)
.where(
models_raid.RaidVolunteer.user_id == user_id,
models_raid.RaidVolunteer.edition_id == edition_id,
)
.values(t_shirt_payment=True),
)
await db.flush()


async def validate_attestation_on_honour(
user_id: str,
edition_id: UUID,
Expand All @@ -590,14 +722,40 @@ async def get_participant_by_user_id(
user_id: str,
edition_id: UUID,
db: AsyncSession,
) -> schemas_raid.RaidParticipantRestricted | None:

participant = await db.execute(
select(models_raid.RaidParticipant)
.where(
models_raid.RaidParticipant.user_id == user_id,
models_raid.RaidParticipant.edition_id == edition_id,
)
.options(
*[selectinload(data) for data in PARTICIPANT_DATA_TO_SELECT],
),
)
model = participant.scalars().first()
return (
schemas_raid.RaidParticipantRestricted.model_validate(model) if model else None
)


async def get_participant_complete_by_user_id(
user_id: str,
edition_id: UUID,
db: AsyncSession,
) -> schemas_raid.RaidParticipant | None:

participant = await db.execute(
select(models_raid.RaidParticipant)
.where(
models_raid.RaidParticipant.user_id == user_id,
models_raid.RaidParticipant.edition_id == edition_id,
)
.options(selectinload("*")),
.options(
*[selectinload(data) for data in PARTICIPANT_DATA_TO_SELECT],
selectinload(models_raid.RaidParticipant.security_file),
),
)
model = participant.scalars().first()
return schemas_raid.RaidParticipant.model_validate(model) if model else None
Expand Down Expand Up @@ -758,6 +916,34 @@ async def get_participant_checkout_by_checkout_id(
return schemas_raid.RaidParticipantCheckout.model_validate(model) if model else None


async def create_volunteer_checkout(
checkout: schemas_raid.RaidVolunteerCheckout,
db: AsyncSession,
) -> None:
db.add(
models_raid.RaidVolunteerCheckout(
id=str(uuid.uuid4()),
volunteer_user_id=checkout.volunteer_user_id,
edition_id=checkout.edition_id,
checkout_id=checkout.checkout_id,
),
)
await db.flush()


async def get_volunteer_checkout_by_checkout_id(
checkout_id: str,
db: AsyncSession,
) -> schemas_raid.RaidVolunteerCheckout | None:
checkout = await db.execute(
select(models_raid.RaidVolunteerCheckout).where(
models_raid.RaidVolunteerCheckout.checkout_id == checkout_id,
),
)
model = checkout.scalars().first()
return schemas_raid.RaidVolunteerCheckout.model_validate(model) if model else None


# --- Edition CRUDs ------------------------------------------------------


Expand Down Expand Up @@ -907,6 +1093,8 @@ async def create_volunteer(
is_special_driver=volunteer.is_special_driver,
is_utility_vehicle_driver=volunteer.is_utility_vehicle_driver,
is_parcours_helper=volunteer.is_parcours_helper,
payment=volunteer.payment,
t_shirt_payment=volunteer.t_shirt_payment,
),
)
await db.flush()
Expand Down
Loading
Loading