feat: compute the mairie form values from a pending event
Pure, testable core of the prefill: payload + config -> flat values ready to lay down. What the machine does not know stays empty and is listed in aCompleter, never invented (no fake 00:00, no made-up coordinates). DTEND is exclusive (RFC 5545): a festival on the 18th-20th carries DTEND=21, so the end date must step back one day, in local components for all-day events. Without it, a wrong date reaches a moderated form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b103ab78ec
commit
78153557dd
@@ -0,0 +1,133 @@
|
||||
// Cœur métier du pré-remplissage : traduit le payload déposé par la liste en
|
||||
// valeurs prêtes à poser dans le formulaire mairie. Fonctions PURES, aucun DOM
|
||||
// (c'est cette partie-là qui est testée ; le poseur, lui, est fin et impur).
|
||||
//
|
||||
// ⚠️ Ni `import` ni `export`, comme config.js (cf. DECISIONS.md 2026-07-17).
|
||||
|
||||
const LOCALE = "fr-FR";
|
||||
const FUSEAU = "Europe/Paris";
|
||||
|
||||
// Dupliquée depuis transfert.js (ESM, inatteignable depuis un content script
|
||||
// classique). Un test de dérive garantit l'égalité — cf. DECISIONS.md 2026-07-17.
|
||||
const CLE_EVENEMENT_EN_ATTENTE = "evenement-en-attente";
|
||||
|
||||
// Un événement horodaté porte un vrai instant : la mairie attend l'heure de
|
||||
// Cugnaux, quel que soit le fuseau de la machine du bénévole.
|
||||
const FORMAT_DATE_PARIS = new Intl.DateTimeFormat(LOCALE, {
|
||||
timeZone: FUSEAU,
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
const FORMAT_HEURE_PARIS = new Intl.DateTimeFormat(LOCALE, {
|
||||
timeZone: FUSEAU,
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hourCycle: "h23",
|
||||
});
|
||||
|
||||
globalThis.EchoFormulaire = {
|
||||
CLE_EVENEMENT_EN_ATTENTE,
|
||||
|
||||
// payload (transfert.js) + config (config.js) → objet plat, prêt à poser.
|
||||
// Ce qu'on ignore reste vide et part dans `aCompleter` : jamais d'invention.
|
||||
calculerValeurs(payload, config) {
|
||||
const debut = new Date(payload.debut);
|
||||
const fin = new Date(payload.fin);
|
||||
|
||||
const valeurs = {
|
||||
organisateur: config.organisateur,
|
||||
email: config.email,
|
||||
titre: payload.titre,
|
||||
description: payload.description,
|
||||
theme: config.themeDefaut,
|
||||
tarifs: config.tarifs,
|
||||
...(payload.journeeEntiere ? datesFlottantes(debut, fin) : datesHorodatees(debut, fin)),
|
||||
...lieuEtCoordonnees(payload.lieu, config.cafe),
|
||||
};
|
||||
|
||||
return { ...valeurs, aCompleter: listerACompleter(valeurs) };
|
||||
},
|
||||
};
|
||||
|
||||
// Journée entière = date FLOTTANTE (RFC 5545) : composantes locales
|
||||
// uniquement, jamais l'heure, jamais un fuseau — sinon la date glisse d'un jour
|
||||
// hors de +02:00 (cf. DECISIONS.md 2026-07-14).
|
||||
function datesFlottantes(debut, fin) {
|
||||
const dateDebut = formaterDateLocale(debut);
|
||||
const dernierJour = veilleDe(fin);
|
||||
|
||||
return {
|
||||
dateDebut,
|
||||
// Un seul jour, ou DTEND absent (le parseur pose alors fin === debut).
|
||||
dateFin:
|
||||
dernierJour.getTime() <= debut.getTime() ? dateDebut : formaterDateLocale(dernierJour),
|
||||
// Poser « 00:00 » serait exactement le mensonge que la liste refuse déjà.
|
||||
heureDebut: null,
|
||||
heureFin: null,
|
||||
};
|
||||
}
|
||||
|
||||
function datesHorodatees(debut, fin) {
|
||||
const dateDebut = FORMAT_DATE_PARIS.format(debut);
|
||||
const heureDebut = heureDe(debut);
|
||||
|
||||
// DTEND absent : le parseur pose fin === debut. Pas d'heure de fin à
|
||||
// soumettre — mais l'événement a bien lieu ce jour-là.
|
||||
if (fin.getTime() <= debut.getTime()) {
|
||||
return { dateDebut, dateFin: dateDebut, heureDebut, heureFin: null };
|
||||
}
|
||||
|
||||
// Jour de fin calculé à Paris : un 22:00 → 02:00 change de jour.
|
||||
return {
|
||||
dateDebut,
|
||||
dateFin: FORMAT_DATE_PARIS.format(fin),
|
||||
heureDebut,
|
||||
heureFin: heureDe(fin),
|
||||
};
|
||||
}
|
||||
|
||||
// Le formulaire attend deux selects [HH, MM], zéro-paddés.
|
||||
function heureDe(date) {
|
||||
return FORMAT_HEURE_PARIS.format(date).split(":");
|
||||
}
|
||||
|
||||
function formaterDateLocale(date) {
|
||||
const jour = String(date.getDate()).padStart(2, "0");
|
||||
const mois = String(date.getMonth() + 1).padStart(2, "0");
|
||||
return `${jour}/${mois}/${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
// DTEND est EXCLUSIF (RFC 5545) : un événement du 18 porte DTEND=19, un festival
|
||||
// du 18 au 20 porte DTEND=21. Sans ce recul d'un jour, on soumet à une mairie
|
||||
// qui modère une date de fin FAUSSE. Composantes locales, jamais « -24h ».
|
||||
function veilleDe(date) {
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
|
||||
}
|
||||
|
||||
// La majorité des événements n'a pas de LOCATION : ils se tiennent au café.
|
||||
function lieuEtCoordonnees(lieu, cafe) {
|
||||
const saisi = lieu.trim();
|
||||
if (!saisi) {
|
||||
return { adresse: cafe.adresse, coordonnees: `${cafe.coords}|${cafe.adresse}` };
|
||||
}
|
||||
|
||||
// Lieu ailleurs : on ne sait pas géocoder. Inventer un lat|lng serait pire
|
||||
// qu'un champ vide → l'humain place le point sur la carte.
|
||||
return { adresse: saisi, coordonnees: "" };
|
||||
}
|
||||
|
||||
// Toujours honnête : ce que la machine ne sait pas est dit, pas masqué.
|
||||
function listerACompleter({ heureDebut, heureFin, coordonnees, organisateur, email, titre }) {
|
||||
const aCompleter = [
|
||||
"l'image (obligatoire)", // un script ne peut pas remplir un <input type=file>
|
||||
"le thème", // le calendrier n'en a pas : le défaut est à confirmer
|
||||
];
|
||||
|
||||
if (heureDebut === null || heureFin === null) aCompleter.push("l'heure");
|
||||
if (coordonnees === "") aCompleter.push("le lieu exact sur la carte");
|
||||
if (!organisateur || !email) aCompleter.push("l'organisateur et l'email (config.local.json)");
|
||||
if (titre === "") aCompleter.push("le titre");
|
||||
|
||||
return aCompleter;
|
||||
}
|
||||
Reference in New Issue
Block a user