// Types miroir des schémas de l'API back-end (`backend/app/api/schemas.py`). // Toute évolution du contrat doit être répercutée ici. export type PositionCategory = | 'BAR' | 'TICKETING' | 'WELCOME' | 'PARKING' | 'CLEANING' | 'CATERING' | 'SETUP' | 'STAGE' | 'OTHER'; // Depuis l'Étape 9 back-end : une affectation est valide dès sa création, // il n'y a plus de statut (proposée / confirmée…). La retirer = supprimer la ligne. export type AssignmentSource = 'SOLVER' | 'MANUAL'; export type Role = 'COORDINATOR' | 'LEAD' | 'VOLUNTEER'; export type AuthMethod = 'password' | 'magic_link'; export type ViolationCode = | 'SHIFT_OVERLAP' | 'OUTSIDE_AVAILABILITY' | 'DAILY_HOURS_EXCEEDED' | 'INSUFFICIENT_REST'; export type ViolationSeverity = 'BLOCKING' | 'WARNING'; export interface Violation { code: ViolationCode; severity: ViolationSeverity; params: Record; } // --------------------------------------------------------------------------- // // Edition // // --------------------------------------------------------------------------- // export interface EditionRead { id: string; year: number; name: string | null; /** Dates ISO (YYYY-MM-DD). */ start_date: string; end_date: string; retention_days: number | null; created_at: string; updated_at: string; } export interface EditionUpdate { name?: string | null; year?: number; start_date?: string; end_date?: string; retention_days?: number | null; } // --------------------------------------------------------------------------- // // DayPart (créneau type) // // --------------------------------------------------------------------------- // export interface DayPartRead { id: string; edition_id: string; code: string; label: string; /** Heures « HH:MM:SS ». end < start ⇒ passe minuit. */ start_time: string; end_time: string; order: number; created_at: string; updated_at: string; } export interface DayPartCreate { label: string; start_time: string; end_time: string; /** Déduit du label si absent ; figé ensuite. */ code?: string; order?: number; } export interface DayPartUpdate { label?: string; start_time?: string; end_time?: string; order?: number; } export interface GenerateShiftsResult { created: number; } // --------------------------------------------------------------------------- // // Volunteer // // --------------------------------------------------------------------------- // export interface VolunteerRead { id: string; edition_id: string; first_name: string; last_name: string; email: string | null; phone: string | null; comment: string | null; /** Vœux : artistes que le bénévole veut voir. */ artist_ids: string[]; /** Vœux : postes que le bénévole voudrait tenir. */ wished_position_ids: string[]; /** Vœux : bénévoles avec qui il voudrait être affecté (0 à N, à sens unique). */ wished_companion_ids: string[]; created_at: string; updated_at: string; } export interface VolunteerCreate { first_name: string; last_name: string; email?: string | null; phone?: string | null; comment?: string | null; artist_ids?: string[]; wished_position_ids?: string[]; wished_companion_ids?: string[]; } export interface VolunteerUpdate { first_name?: string; last_name?: string; email?: string | null; phone?: string | null; comment?: string | null; /** null / absent = inchangé ; [] = vider. */ artist_ids?: string[] | null; wished_position_ids?: string[] | null; wished_companion_ids?: string[] | null; } /** * Un créneau affecté, enrichi (poste, lieu, référent à contacter) — pour * l'écran bénévole. Évite de donner à un compte VOLUNTEER un accès en * lecture aux postes/créneaux de toute l'édition (réservé à COORD/LEAD). */ export interface VolunteerPlanningEntry { assignment_id: string; shift_id: string; starts_at: string; ends_at: string; position_name: string; position_location: string | null; /** null si le poste n'a pas de référent, ou si son compte n'est pas * rattaché à une fiche bénévole (pas de téléphone à donner alors). */ referent_name: string | null; referent_phone: string | null; } // --------------------------------------------------------------------------- // // Availability (disponibilités déclarées par le bénévole) // // --------------------------------------------------------------------------- // export interface AvailabilityWindowInput { /** Date ISO (YYYY-MM-DD). */ date: string; /** Heure « HH:MM » ou « HH:MM:SS ». end <= start ⇒ se termine le lendemain. */ start_time: string; end_time: string; } export interface AvailabilitySet { windows: AvailabilityWindowInput[]; } export interface AvailabilityRead { id: string; edition_id: string; volunteer_id: string; date: string; start_time: string; end_time: string; created_at: string; updated_at: string; } // --------------------------------------------------------------------------- // // Artist (programmation) // // --------------------------------------------------------------------------- // export interface ArtistRead { id: string; edition_id: string; name: string; /** Jour de passage, date ISO (YYYY-MM-DD). */ performance_date: string; /** Heures « HH:MM:SS ». end_time <= start_time ⇒ finit le lendemain. */ start_time: string; end_time: string; created_at: string; updated_at: string; } export interface ArtistCreate { name: string; performance_date: string; start_time: string; end_time: string; } export interface ArtistUpdate { name?: string; performance_date?: string; start_time?: string; end_time?: string; } // --------------------------------------------------------------------------- // // Import de bénévoles (fichier CSV / Excel) // // --------------------------------------------------------------------------- // export type ImportFieldKey = | 'first_name' | 'last_name' | 'email' | 'phone' | 'comment'; export interface ImportColumnMapping { first_name: string; last_name: string; email?: string | null; phone?: string | null; comment?: string | null; } export interface ImportMapping { dry_run: boolean; columns: ImportColumnMapping; } export interface ImportPreviewRow { line: number; values: Record; } export interface ImportAnalyzeResponse { filename: string; format: 'csv' | 'xlsx'; headers: string[]; row_count: number; preview: ImportPreviewRow[]; suggested_mapping: Partial>; } export type ImportSeverity = 'ERROR' | 'WARNING'; export type ImportRowAction = 'CREATED' | 'UPDATED' | 'SKIPPED'; export interface ImportIssue { code: string; severity: ImportSeverity; field?: string | null; params: Record; } export interface ImportRowReport { line: number; action: ImportRowAction; volunteer_id: string | null; issues: ImportIssue[]; } export interface ImportReport { dry_run: boolean; total_rows: number; created: number; updated: number; skipped: number; rows: ImportRowReport[]; } // --------------------------------------------------------------------------- // // Position // // --------------------------------------------------------------------------- // export interface PositionRead { id: string; edition_id: string; name: string; location: string | null; category: PositionCategory; lead_account_id: string | null; created_at: string; updated_at: string; } export interface PositionCreate { name: string; location?: string | null; category: PositionCategory; lead_account_id?: string | null; } export interface PositionUpdate { name?: string; location?: string | null; category?: PositionCategory; lead_account_id?: string | null; } // --------------------------------------------------------------------------- // // Shift // // --------------------------------------------------------------------------- // export interface ShiftRead { id: string; edition_id: string; position_id: string; /** Créneau type d'origine, ou null pour un créneau ad hoc. */ day_part_id: string | null; /** Datetime naïf, heure locale Europe/Paris (ISO sans fuseau). */ starts_at: string; ends_at: string; min_staff: number; ideal_staff: number; created_at: string; updated_at: string; } export interface ShiftCreate { position_id: string; starts_at: string; ends_at: string; min_staff?: number; ideal_staff?: number; } export interface ShiftUpdate { position_id?: string; starts_at?: string; ends_at?: string; min_staff?: number; ideal_staff?: number; } // --------------------------------------------------------------------------- // // Assignment // // --------------------------------------------------------------------------- // export interface AssignmentRead { id: string; edition_id: string; volunteer_id: string; shift_id: string; source: AssignmentSource; locked: boolean; created_at: string; updated_at: string; } export interface AssignmentCreate { volunteer_id: string; shift_id: string; source?: AssignmentSource; locked?: boolean; } export interface AssignmentUpdate { source?: AssignmentSource; locked?: boolean; } export interface AssignmentWriteResult { assignment: AssignmentRead; warnings: Violation[]; } export interface AssignmentValidateRequest { volunteer_id: string; shift_id: string; } export interface AssignmentValidateResult { blocking: boolean; violations: Violation[]; } // --------------------------------------------------------------------------- // // Solveur (suggestions, CP-SAT) — ne place jamais personne // // --------------------------------------------------------------------------- // export interface SolverSuggestion { shift_id: string; /** Bénévoles suggérés pour ce créneau, meilleur en tête. */ volunteer_ids: string[]; } export interface SolverRunResult { suggestions: SolverSuggestion[]; shifts_considered: number; volunteers_considered: number; } // --------------------------------------------------------------------------- // // Matrice poste × créneau // // --------------------------------------------------------------------------- // export interface MatrixShift { shift_id: string; starts_at: string; ends_at: string; min_staff: number; ideal_staff: number; /** Nombre d'affectations sur ce créneau. */ assigned: number; } export interface MatrixPosition { id: string; name: string; category: PositionCategory; location: string | null; shifts: MatrixShift[]; } export interface MatrixResponse { edition_id: string; positions: MatrixPosition[]; } // --------------------------------------------------------------------------- // // Badges (planches PDF) // // --------------------------------------------------------------------------- // export type BadgeFieldType = 'FULL_NAME' | 'POSITIONS' | 'CATEGORY' | 'ROLE'; export type BadgeAlign = 'LEFT' | 'CENTER' | 'RIGHT'; export interface BadgeFieldIn { field_type: BadgeFieldType; x_mm: number; y_mm: number; width_mm: number; height_mm: number; font_size: number; align: BadgeAlign; bold: boolean; color: string; } export interface BadgeFieldsSet { fields: BadgeFieldIn[]; } export interface BadgeFieldRead extends BadgeFieldIn { id: string; edition_id: string; } export interface BadgeTemplateRead { edition_id: string; has_front_image: boolean; has_back_image: boolean; fields: BadgeFieldRead[]; } export interface BadgeGenerateRequest { volunteer_ids?: string[] | null; } // --------------------------------------------------------------------------- // // Authentification // // --------------------------------------------------------------------------- // export interface AccountRead { id: string; edition_id: string; email: string; role: Role; volunteer_id: string | null; /** "password" (coordination) ou "magic_link" (référents, bénévoles). */ auth_method: AuthMethod; created_at: string; updated_at: string; } export interface AccountCreate { email: string; role: Role; volunteer_id?: string | null; /** Requis pour un compte COORDINATOR, interdit sinon. */ password?: string | null; } export interface AccountUpdate { /** Seulement entre LEAD et VOLUNTEER — jamais vers/depuis COORDINATOR. */ role?: Role; } export interface TokenResponse { access_token: string; token_type: string; account: AccountRead; } export interface LoginRequest { email: string; password: string; } export interface MagicLinkRequest { email: string; } export interface VerifyRequest { token: string; } // --------------------------------------------------------------------------- // // Notifications par email // // --------------------------------------------------------------------------- // /** * `subject` / `body` sont des modèles. Variables : {prenom} {nom} * {nb_creneaux} {nb_heures} {creneaux} {lien_connexion} (ce dernier déclenche * l'émission d'un lien magique personnel). */ export interface PlanningNotificationRequest { subject?: string | null; body?: string | null; /** null / absent = tous les bénévoles de l'édition qui ont un email. */ volunteer_ids?: string[] | null; } export interface PlanningNotificationResult { queued: number; skipped_no_email: number; recipients: string[]; }