298 lines
8.8 KiB
TypeScript
298 lines
8.8 KiB
TypeScript
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<Record<SalleUsageType, Partial<Record<SalleReservationFrequency, number>>>>;
|
||
};
|
||
|
||
export const SALLE_USAGE_TYPE_LABELS: Record<SalleUsageType, string> = {
|
||
conventionne: "Conventionné",
|
||
occasionnel: "Occasionnel",
|
||
};
|
||
|
||
export const SALLE_FREQUENCY_LABELS: Record<SalleReservationFrequency, string> = {
|
||
demi_journee: "Demi-journée",
|
||
journee: "Journée",
|
||
mensuel: "Mensuel",
|
||
};
|
||
|
||
export const SALLE_WORKFLOW_QUOTE_STATUS_LABELS: Record<SalleWorkflowQuoteStatus, string> = {
|
||
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<SalleWorkflowDirectorStatus, string> = {
|
||
a_transmettre: "À transmettre à la Directrice",
|
||
en_attente_signature: "En attente de signature",
|
||
signee: "Signée",
|
||
};
|
||
|
||
export const SALLE_WORKFLOW_PAYMENT_STATUS_LABELS: Record<SalleWorkflowPaymentStatus, string> = {
|
||
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;
|
||
}
|