"""Construit une `ValidationInput` depuis la base pour un couple (bénévole, créneau). Couche de liaison : c'est le seul endroit du module `validation` qui connaît la base.""" import uuid from sqlmodel import Session, select from app.models import ( ACTIVE_ASSIGNMENT_STATUSES, Assignment, Availability, DayPart, Edition, Shift, ShiftRequiredSkill, Skill, Volunteer, VolunteerSkill, ) from app.validation.models import ( AvailabilityDeclaration, DayPartWindow, ShiftInfo, ValidationInput, ) def _volunteer_skill_codes( session: Session, volunteer_id: uuid.UUID ) -> frozenset[str]: return frozenset( session.exec( select(Skill.code) .join(VolunteerSkill, VolunteerSkill.skill_id == Skill.id) .where(VolunteerSkill.volunteer_id == volunteer_id) ) ) def _shift_required_skill_codes( session: Session, shift_id: uuid.UUID ) -> frozenset[str]: return frozenset( session.exec( select(Skill.code) .join( ShiftRequiredSkill, ShiftRequiredSkill.skill_id == Skill.id ) .where(ShiftRequiredSkill.shift_id == shift_id) ) ) def build_validation_input( *, session: Session, edition: Edition, volunteer: Volunteer, shift: Shift, exclude_assignment_id: uuid.UUID | None = None, ) -> ValidationInput: day_parts = list( session.exec(select(DayPart).where(DayPart.edition_id == edition.id)) ) code_by_day_part_id = {dp.id: dp.code for dp in day_parts} stmt = ( select(Shift) .join(Assignment, Assignment.shift_id == Shift.id) .where(Assignment.volunteer_id == volunteer.id) .where( Assignment.status.in_(tuple(ACTIVE_ASSIGNMENT_STATUSES)) ) .where(Shift.id != shift.id) ) if exclude_assignment_id is not None: stmt = stmt.where(Assignment.id != exclude_assignment_id) existing_shifts = tuple( ShiftInfo(id=str(s.id), starts_at=s.starts_at, ends_at=s.ends_at) for s in session.exec(stmt) ) declarations = tuple( AvailabilityDeclaration( date=a.date, day_part_code=code_by_day_part_id.get(a.day_part_id, ""), status=a.status, ) for a in session.exec( select(Availability).where( Availability.volunteer_id == volunteer.id ) ) ) return ValidationInput( candidate_shift=ShiftInfo( id=str(shift.id), starts_at=shift.starts_at, ends_at=shift.ends_at, required_skill_codes=_shift_required_skill_codes(session, shift.id), ), volunteer_skill_codes=_volunteer_skill_codes(session, volunteer.id), existing_shifts=existing_shifts, availabilities=declarations, day_parts=tuple( DayPartWindow(dp.code, dp.start_time, dp.end_time, dp.order) for dp in day_parts ), )