La photo recouvrait le nom et le texte libre (champs dessinés dans l'ordre alphabétique de leur type). BadgeField.z_order (migration 0028, champs existants renumérotés photo dessous) fixe l'ordre de dessin du PDF et de l'aperçu ; boutons « Premier plan / Avancer / Reculer / Arrière-plan » dans l'inspecteur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
817 lines
24 KiB
TypeScript
817 lines
24 KiB
TypeScript
// 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';
|
||
|
||
export type ViolationSeverity = 'BLOCKING' | 'WARNING';
|
||
|
||
export interface Violation {
|
||
code: ViolationCode;
|
||
severity: ViolationSeverity;
|
||
params: Record<string, string | number>;
|
||
}
|
||
|
||
// --------------------------------------------------------------------------- //
|
||
// 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[];
|
||
/** Renseignés par le formulaire public d'inscription (voir schema forms). */
|
||
has_photo: boolean;
|
||
companion_wish_note: string | null;
|
||
/** Texte libre imprimé sur le badge (champ `FREE_TEXT`), saisi par la
|
||
* coordination. */
|
||
badge_text: string | null;
|
||
/** Exemplaires de son badge imprimés sur les planches (1 par défaut). */
|
||
badge_count: number;
|
||
self_declared_available: boolean;
|
||
willing_to_pay_fee: boolean;
|
||
/** Paiement de la cotisation via SumUp (voir schema forms) —
|
||
* `fee_payment_label`/`fee_amount` : copie figée du type de paiement
|
||
* choisi (pas la config actuelle). `fee_payment_status` reste `null` tant
|
||
* qu'aucun checkout n'a jamais été créé pour ce bénévole. */
|
||
fee_payment_label: string | null;
|
||
fee_amount: number | null;
|
||
fee_payment_status: FeePaymentStatus | null;
|
||
fee_paid_at: string | null;
|
||
/** Transaction SumUp qui a réglé la cotisation : `fee_transaction_code` est
|
||
* le code affiché dans le tableau de bord SumUp (ex. `TEENSK4W2K`),
|
||
* `fee_transaction_id` l'identifiant technique. `null` tant que non payée
|
||
* (ou payée avant que l'appli ne l'enregistre). */
|
||
fee_transaction_code: string | null;
|
||
fee_transaction_id: string | null;
|
||
/** Coordonnées pour un reçu fiscal (cerfa) — seulement si payé via un type
|
||
* de paiement « mécénat ». */
|
||
mecenat_address: string | null;
|
||
mecenat_city: string | null;
|
||
mecenat_postal_code: string | null;
|
||
mecenat_country: string | null;
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
|
||
export type FeePaymentStatus = 'PENDING' | 'PAID' | 'FAILED' | 'EXPIRED';
|
||
|
||
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;
|
||
companion_wish_note?: string | null;
|
||
badge_text?: string | null;
|
||
/** 1 à 100. */
|
||
badge_count?: number | null;
|
||
self_declared_available?: boolean | null;
|
||
willing_to_pay_fee?: boolean | 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;
|
||
}
|
||
|
||
// --------------------------------------------------------------------------- //
|
||
// 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<string, string>;
|
||
}
|
||
|
||
export interface ImportAnalyzeResponse {
|
||
filename: string;
|
||
format: 'csv' | 'xlsx';
|
||
headers: string[];
|
||
row_count: number;
|
||
preview: ImportPreviewRow[];
|
||
suggested_mapping: Partial<Record<ImportFieldKey, string>>;
|
||
}
|
||
|
||
export type ImportSeverity = 'ERROR' | 'WARNING';
|
||
export type ImportRowAction = 'CREATED' | 'UPDATED' | 'SKIPPED';
|
||
|
||
export interface ImportIssue {
|
||
code: string;
|
||
severity: ImportSeverity;
|
||
field?: string | null;
|
||
params: Record<string, string | number | null>;
|
||
}
|
||
|
||
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) //
|
||
// --------------------------------------------------------------------------- //
|
||
/** `PHOTO` : photo du bénévole ajustée au cadre ; `FREE_TEXT` : texte libre
|
||
* de sa fiche (`VolunteerRead.badge_text`), saisi par la coordination. */
|
||
export type BadgeFieldType =
|
||
| 'FULL_NAME'
|
||
| 'POSITIONS'
|
||
| 'CATEGORY'
|
||
| 'ROLE'
|
||
| 'PHOTO'
|
||
| 'FREE_TEXT';
|
||
export type BadgeAlign = 'LEFT' | 'CENTER' | 'RIGHT';
|
||
/** Face du badge où se trouve un champ : chaque type de champ peut exister
|
||
* une fois par face. */
|
||
export type BadgeFace = 'FRONT' | 'BACK';
|
||
/** Forme du cadre de la photo (`PHOTO`). */
|
||
export type BadgePhotoShape = 'RECT' | 'OVAL';
|
||
|
||
export interface BadgeFieldIn {
|
||
field_type: BadgeFieldType;
|
||
/** Défaut `FRONT`. */
|
||
face: BadgeFace;
|
||
x_mm: number;
|
||
y_mm: number;
|
||
width_mm: number;
|
||
height_mm: number;
|
||
font_size: number;
|
||
align: BadgeAlign;
|
||
bold: boolean;
|
||
color: string;
|
||
/** Fond de la zone du champ (`#rrggbb`), `null` = transparent. */
|
||
bg_color: string | null;
|
||
/** Réduit la taille du texte (par pas de 0,5 pt, jusqu'à 4 pt) quand il
|
||
* ne tient pas dans le cadre. */
|
||
auto_fit: boolean;
|
||
/** `PHOTO` seulement : rectangle ou ovale inscrit dans le cadre. */
|
||
photo_shape: BadgePhotoShape;
|
||
/** Ordre d'empilement sur la face : un champ recouvre ceux d'un
|
||
* `z_order` inférieur (photo dessous à égalité). */
|
||
z_order: number;
|
||
/** `FREE_TEXT` seulement : texte commun à tous les badges, remplacé pour
|
||
* un bénévole par le « Texte du badge » de sa fiche (`badge_text`). */
|
||
text?: string | null;
|
||
}
|
||
|
||
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;
|
||
/** Dérivée de l'image du recto (300 dpi supposés) ; format CR80 par défaut
|
||
* tant qu'aucune image n'est importée. */
|
||
badge_width_mm: number;
|
||
badge_height_mm: number;
|
||
fields: BadgeFieldRead[];
|
||
}
|
||
|
||
export interface BadgeGenerateRequest {
|
||
volunteer_ids?: string[] | null;
|
||
}
|
||
|
||
/** Redimensionne le badge indépendamment d'un nouvel import d'image — ex.
|
||
* agrandir proportionnellement une image importée à basse résolution. */
|
||
export interface BadgeSizeUpdate {
|
||
badge_width_mm: number;
|
||
badge_height_mm: number;
|
||
}
|
||
|
||
/** Feuilles de poste (PDF) : une page par poste et par jour, créneaux +
|
||
* bénévoles affectés + téléphone. */
|
||
export interface PositionSheetsRequest {
|
||
// null/absent = tous les postes de l'édition.
|
||
position_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[];
|
||
}
|
||
|
||
// --------------------------------------------------------------------------- //
|
||
// Formulaire public d'inscription //
|
||
// --------------------------------------------------------------------------- //
|
||
// Nom/prénom toujours collectés, pas dans cette liste (voir app/api/forms.py).
|
||
export type FormFieldType =
|
||
| 'EMAIL'
|
||
| 'PHONE'
|
||
| 'POSITION_WISHES'
|
||
| 'ARTIST_WISHES'
|
||
| 'COMPANION_WISH'
|
||
| 'AVAILABLE'
|
||
| 'WILLING_TO_PAY_FEE'
|
||
| 'PHOTO'
|
||
| 'COMMENT';
|
||
|
||
export interface FormFieldsSet {
|
||
fields: FormFieldType[];
|
||
}
|
||
|
||
export interface FormTemplateRead {
|
||
edition_id: string;
|
||
is_open: boolean;
|
||
fields: FormFieldType[];
|
||
has_logo: boolean;
|
||
description: string | null;
|
||
sumup_merchant_code: string | null;
|
||
/** `sumup_api_key` (secret) n'est jamais renvoyé — seulement s'il est défini. */
|
||
sumup_api_key_set: boolean;
|
||
/** Jeton requis pour atteindre `/inscription/:token` — voir
|
||
* CLAUDE.md § 2, `FormTemplate`. Visible ici seulement (COORDINATION). */
|
||
access_token: string;
|
||
}
|
||
|
||
/** `undefined`/absent = inchangé pour chaque champ. Chaîne vide sur
|
||
* `sumup_api_key` efface le secret enregistré. */
|
||
export interface FormTemplateUpdate {
|
||
is_open?: boolean;
|
||
description?: string | null;
|
||
sumup_merchant_code?: string | null;
|
||
sumup_api_key?: string | null;
|
||
}
|
||
|
||
/** Un type de paiement proposé sur le formulaire public (ex. « Cotisation
|
||
* classique », « Mécénat ») — libellé + montant fixe, configurés librement
|
||
* par la coordination (comme les créneaux types `DayPart`). */
|
||
export interface FormPaymentOptionRead {
|
||
id: string;
|
||
edition_id: string;
|
||
label: string;
|
||
amount: number;
|
||
order: number;
|
||
/** Type « mécénat » : le bénévole passe par une étape d'adresse postale
|
||
* avant le paiement (reçu fiscal / cerfa). */
|
||
requires_mecenat_info: boolean;
|
||
/** Montant libre : le bénévole saisit son montant, `amount` = minimum. */
|
||
free_amount: boolean;
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
|
||
export interface FormPaymentOptionCreate {
|
||
label: string;
|
||
amount: number;
|
||
order?: number;
|
||
requires_mecenat_info?: boolean;
|
||
free_amount?: boolean;
|
||
}
|
||
|
||
export interface FormPaymentOptionUpdate {
|
||
label?: string;
|
||
amount?: number;
|
||
order?: number;
|
||
requires_mecenat_info?: boolean;
|
||
free_amount?: boolean;
|
||
}
|
||
|
||
export interface PublicPositionOption {
|
||
id: string;
|
||
name: string;
|
||
}
|
||
|
||
export interface PublicArtistOption {
|
||
id: string;
|
||
name: string;
|
||
performance_date: string;
|
||
}
|
||
|
||
/** Type de paiement affiché sur le formulaire public — un montant n'est pas
|
||
* un secret, contrairement aux identifiants SumUp. */
|
||
export interface PublicPaymentOption {
|
||
id: string;
|
||
label: string;
|
||
/** Montant fixe, ou minimum si `free_amount`. */
|
||
amount: number;
|
||
requires_mecenat_info: boolean;
|
||
free_amount: boolean;
|
||
}
|
||
|
||
/** Tout ce qu'il faut pour afficher le formulaire public — jamais plus. */
|
||
export interface PublicFormConfig {
|
||
is_open: boolean;
|
||
fields: FormFieldType[];
|
||
positions: PublicPositionOption[];
|
||
artists: PublicArtistOption[];
|
||
has_logo: boolean;
|
||
description: string | null;
|
||
/** `true` si l'édition a configuré SumUp (identifiants + au moins un type
|
||
* de paiement). */
|
||
sumup_enabled: boolean;
|
||
payment_options: PublicPaymentOption[];
|
||
}
|
||
|
||
/** Trois formes possibles, selon le choix de paiement (voir
|
||
* `app/api/forms.py:submit_public_form`) :
|
||
* - aucun paiement en ligne (case désactivée, ou « je paierai sur place ») :
|
||
* `volunteer_id` seul, le bénévole est déjà en base ;
|
||
* - paiement en ligne, type « mécénat » : `pending_token` +
|
||
* `requires_mecenat_info=true`, rien en base encore — direction l'étape
|
||
* d'adresse (`submitMecenatInfo`) ;
|
||
* - paiement en ligne, type simple : `pending_token` + `sumup_checkout_url`,
|
||
* direction la page de paiement SumUp. */
|
||
export interface FormSubmissionResult {
|
||
volunteer_id: string | null;
|
||
pending_token: string | null;
|
||
requires_mecenat_info: boolean;
|
||
sumup_checkout_url: string | null;
|
||
}
|
||
|
||
/** Étape d'adresse postale d'un type de paiement « mécénat » — tous
|
||
* obligatoires, nécessaires à l'édition d'un reçu fiscal (cerfa). */
|
||
export interface MecenatInfoSubmit {
|
||
address: string;
|
||
city: string;
|
||
postal_code: string;
|
||
country: string;
|
||
}
|
||
|
||
/** Réponse de la vérification en direct du paiement (page de confirmation) —
|
||
* interrogée jusqu'à ce que le statut ne soit plus `PENDING`. */
|
||
export interface PendingSubmissionStatus {
|
||
status: FeePaymentStatus;
|
||
label: string;
|
||
amount: number;
|
||
volunteer_id: string | null;
|
||
}
|
||
|
||
/** Champs du formulaire public — envoyés en `multipart/form-data` (photo
|
||
* possible). `position_wish_ids`/`artist_wish_ids` : JSON stringifié
|
||
* (`'["id1","id2"]'`), pas de tableau natif possible en multipart. */
|
||
export interface PublicFormSubmission {
|
||
first_name: string;
|
||
last_name: string;
|
||
hcaptcha_token: string;
|
||
email?: string;
|
||
phone?: string;
|
||
comment?: string;
|
||
companion_wish_note?: string;
|
||
available?: boolean;
|
||
/** Type de paiement choisi (voir `PublicPaymentOption`) — absent = « je
|
||
* paierai sur place » (aucun paiement en ligne). */
|
||
payment_option_id?: string;
|
||
/** Montant saisi, pour un type de paiement à montant libre seulement. */
|
||
payment_amount?: string;
|
||
position_wish_ids?: string[];
|
||
artist_wish_ids?: string[];
|
||
photo?: File | null;
|
||
}
|
||
|
||
// --------------------------------------------------------------------------- //
|
||
// Statistiques d'une édition (`GET /editions/{id}/stats`, app/api/stats.py) //
|
||
// --------------------------------------------------------------------------- //
|
||
export interface StatsRegistrationDay {
|
||
date: string;
|
||
count: number;
|
||
cumulative: number;
|
||
}
|
||
|
||
export interface StatsAnswers {
|
||
total: number;
|
||
email: number;
|
||
phone: number;
|
||
photo: number;
|
||
comment: number;
|
||
companion_note: number;
|
||
available: number;
|
||
position_wishes: number;
|
||
artist_wishes: number;
|
||
companion_wishes: number;
|
||
}
|
||
|
||
export interface StatsPaymentOption {
|
||
label: string;
|
||
mecenat: boolean;
|
||
count: number;
|
||
amount_total: number;
|
||
}
|
||
|
||
export interface StatsNeedDay {
|
||
date: string;
|
||
shifts: number;
|
||
min_hours: number;
|
||
ideal_hours: number;
|
||
assigned_hours: number;
|
||
}
|
||
|
||
export interface StatsNeedPosition {
|
||
position_id: string;
|
||
name: string;
|
||
shifts: number;
|
||
min_slots: number;
|
||
ideal_slots: number;
|
||
assigned: number;
|
||
/** Places pourvues, plafonnées créneau par créneau à l'effectif idéal. */
|
||
filled: number;
|
||
wishes: number;
|
||
}
|
||
|
||
export interface EditionStats {
|
||
registrations: { total: number; last_7_days: number; by_day: StatsRegistrationDay[] };
|
||
answers: StatsAnswers;
|
||
payments: {
|
||
online: number;
|
||
on_site: number;
|
||
none: number;
|
||
/** Formulaires remplis dont le paiement en ligne n'a jamais abouti. */
|
||
pending_unfinished: number;
|
||
total_collected: number;
|
||
mecenat_count: number;
|
||
mecenat_total: number;
|
||
by_option: StatsPaymentOption[];
|
||
};
|
||
artist_wishes: Array<{ id: string; name: string; date: string; count: number }>;
|
||
needs: {
|
||
by_day: StatsNeedDay[];
|
||
by_position: StatsNeedPosition[];
|
||
volunteers_with_assignment: number;
|
||
};
|
||
}
|