export type SalleUsageType = "conventionne" | "occasionnel"; export type SalleReservationFrequency = "demi_journee" | "journee" | "mensuel"; export type SalleWorkflowQuoteStatus = "a_preparer" | "en_attente_association" | "accepte" | "refuse"; export type SalleWorkflowDirectorStatus = "a_transmettre" | "en_attente_signature" | "signee"; export type SalleWorkflowPaymentStatus = "en_attente_paiement" | "paiement_partiel" | "paiement_recu" | "paiement_valide"; export type SallePricingLineItem = { salleId: string; salleNom: string; categoryLabel: string; usageType: SalleUsageType; frequency: SalleReservationFrequency; quantity: number; unitAmountCents: number; totalAmountCents: number; }; export type SallePricingSummary = { usageType: SalleUsageType; frequency: SalleReservationFrequency; quantity: number; lineItems: SallePricingLineItem[]; totalAmountCents: number; unsupportedSalles: Array<{ salleId: string; salleNom: string; }>; }; export type SalleWorkflowData = { usageType?: SalleUsageType; frequency?: SalleReservationFrequency; conditionsFinancieresText?: string; decisionAdministrativeText?: string; pricing?: SallePricingSummary; quoteStatus?: SalleWorkflowQuoteStatus; quoteSentAt?: string; quoteRespondedAt?: string; quotePdfUrl?: string; quotePdfName?: string; administrativeDecisionPdfUrl?: string; administrativeDecisionPdfName?: string; directorStatus?: SalleWorkflowDirectorStatus; directorTransmissionAt?: string; directorSignedAt?: string; directorSignedByUserId?: number; directorSignedByName?: string; directorReturnComment?: string; directorReturnedAt?: string; signedQuotePdfUrl?: string; signedQuotePdfName?: string; signedDecisionPdfUrl?: string; signedDecisionPdfName?: string; signedRequestPdfUrl?: string; signedRequestPdfName?: string; invoicePdfUrl?: string; invoicePdfName?: string; invoiceGeneratedAt?: string; paymentStatus?: SalleWorkflowPaymentStatus; paymentDueDate?: string; amountReceivedCents?: number; paymentReceivedAt?: string; paymentValidatedAt?: string; paymentReference?: string; paymentNotes?: string; finalNotificationSentAt?: string; }; type SalleTariffDefinition = { salleId: string; salleNom: string; categoryLabel: string; pricing: Partial>>>; }; export const SALLE_USAGE_TYPE_LABELS: Record = { conventionne: "Conventionné", occasionnel: "Occasionnel", }; export const SALLE_FREQUENCY_LABELS: Record = { demi_journee: "Demi-journée", journee: "Journée", mensuel: "Mensuel", }; export const SALLE_WORKFLOW_QUOTE_STATUS_LABELS: Record = { a_preparer: "À préparer", en_attente_association: "En attente de l'association", accepte: "Accepté par l'association", refuse: "Refusé par l'association", }; export const SALLE_WORKFLOW_DIRECTOR_STATUS_LABELS: Record = { a_transmettre: "À transmettre à la Directrice", en_attente_signature: "En attente de signature", signee: "Signée", }; export const SALLE_WORKFLOW_PAYMENT_STATUS_LABELS: Record = { en_attente_paiement: "En attente de paiement", paiement_partiel: "Paiement partiel", paiement_recu: "Paiement reçu", paiement_valide: "Paiement validé", }; const SALLE_TARIFFS: SalleTariffDefinition[] = [ { salleId: "bureau_11_17", salleNom: "Bureau 11–17 m²", categoryLabel: "Bureau 11–17 m²", pricing: { conventionne: { journee: 1216, mensuel: 18819, }, }, }, { salleId: "salle_toucan", salleNom: "Salle TOUCAN", categoryLabel: "Salle de conférence - Formation", pricing: { conventionne: { demi_journee: 7673, journee: 15345, }, }, }, { salleId: "salle_ibis", salleNom: "Salle IBIS", categoryLabel: "Espace formation", pricing: { conventionne: { demi_journee: 8173, journee: 15845, mensuel: 25000, }, }, }, { salleId: "salle_pelican", salleNom: "Salle PELICAN", categoryLabel: "Espace formation", pricing: { conventionne: { demi_journee: 8173, journee: 15845, mensuel: 25000, }, }, }, { salleId: "hall_amphitheatre", salleNom: "Hall-Amphithéâtre", categoryLabel: "Hall ouvert - 343,34 m²", pricing: { conventionne: { demi_journee: 5000, journee: 10000, }, }, }, { salleId: "dojo", salleNom: "DOJO", categoryLabel: "Dojo", pricing: { conventionne: { mensuel: 65000, }, occasionnel: { mensuel: 25000, }, }, }, ]; export const CONDITIONS_FINANCIERES_SALLE_TEXT = `CONDITIONS FINANCIERES - LOCATION DES ESPACES La mise a disposition d'un espace au sein de la Maison de la Jeunesse des Savanes « Melissa ALVES » est soumise aux conditions suivantes : 1. Tarification Les tarifs appliques sont ceux votes par le Conseil Communautaire, actualises au 1er septembre 2025. Le montant est calcule automatiquement en fonction : de l'espace choisi de la duree (journee, demi-journee, mensuel) du type d'utilisateur (conventionne / occasionnel) 2. Acompte Un acompte peut etre demande selon la nature de l'evenement. 3. Annulation Toute annulation doit etre signalee 48 heures a l'avance pour un eventuel remboursement de l'acompte. 4. Responsabilite L'association est responsable : des participants du public des dommages eventuels du respect des regles de securite Une attestation de responsabilite civile couvrant la periode doit etre fournie. 5. Entretien des locaux Il est interdit de manger dans les salles. Les espaces doivent etre rendus propres et en bon etat.`; export function buildDecisionAdministrativeText(input: { espace: string; dates: string; horaires: string; association: string; }) { return `DECISION ADMINISTRATIVE PRIORITAIRE La presente decision atteste que la Maison de la Jeunesse des Savanes « Melissa ALVES » donne un avis favorable a la mise a disposition de l'espace suivant : Espace : ${input.espace} Dates : ${input.dates} Horaires : ${input.horaires} Association : ${input.association} Cette decision est transmise avant validation finale et ne vaut pas autorisation definitive. La mise a disposition devient effective uniquement apres : validation du devis (si facturation) signature de la Directrice reception de l'attestation de responsabilite civile`; } export function getSalleTariffDefinition(salleId: string) { return SALLE_TARIFFS.find((entry) => entry.salleId === salleId) || null; } export function listReservationDates(start: string, end: string) { if (!start || !end) return [] as string[]; const startDate = new Date(`${start}T12:00:00`); const endDate = new Date(`${end}T12:00:00`); if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()) || startDate > endDate) { return [] as string[]; } const values: string[] = []; const cursor = new Date(startDate); while (cursor <= endDate) { values.push(cursor.toISOString().slice(0, 10)); cursor.setDate(cursor.getDate() + 1); } return values; } export function getReservationUnitQuantity(input: { frequency: SalleReservationFrequency; dateReservation?: string; dateFinReservation?: string; }) { if (input.frequency === "mensuel") return 1; const dates = listReservationDates(input.dateReservation || "", input.dateFinReservation || input.dateReservation || ""); return Math.max(dates.length, 1); } export function computeSallePricing(input: { sallesIds: string[]; usageType: SalleUsageType; frequency: SalleReservationFrequency; dateReservation?: string; dateFinReservation?: string; }) { const quantity = getReservationUnitQuantity(input); const summary: SallePricingSummary = { usageType: input.usageType, frequency: input.frequency, quantity, lineItems: [], totalAmountCents: 0, unsupportedSalles: [], }; input.sallesIds.forEach((salleId) => { const salle = getSalleTariffDefinition(salleId); if (!salle) { summary.unsupportedSalles.push({ salleId, salleNom: salleId }); return; } const unitAmountCents = salle.pricing[input.usageType]?.[input.frequency]; if (typeof unitAmountCents !== "number") { summary.unsupportedSalles.push({ salleId, salleNom: salle.salleNom }); return; } const lineItem: SallePricingLineItem = { salleId, salleNom: salle.salleNom, categoryLabel: salle.categoryLabel, usageType: input.usageType, frequency: input.frequency, quantity, unitAmountCents, totalAmountCents: unitAmountCents * quantity, }; summary.lineItems.push(lineItem); summary.totalAmountCents += lineItem.totalAmountCents; }); return summary; }