Fichier organisation

Bonjour à tous, j’ai commencé à faire mon fichier sheets avec des script sur chat gpt mais je tourne en rond je n’arrive pas à avancé, la structure est déjà bien commencé mais impossible de finir. j’aurais besoins d’un coup de mains svp

Bonjour Latelierdu7 !
C’est en effet un des grands problèmes de la conception de script par IA, si je peux vous donner quelques pistes pour améliorer votre script / votre manière de les concevoir :

L’utilisation de l’intelligence artificielle est beaucoup plus précise et efficace lorsque nous disposons de quelques notions de base, afin de la ré-orienter vers des bonnes pistes une fois qu’elle commence à tourner en rond :slight_smile:

PS : N’hésitez pas à utiliser ensuite d’autres modèles comme Claude ou Gemini pour la génération de vos codes.

Très belle journée à vous !

merci beaucoup Reminc, sur le forum il y a personne qui serait intéressé de me le faire moyennant finance ?

// Fichier : formulaire.gs

/**

  • Ajoute les menus “:date: Agenda” et “:outbox_tray: Formulaire” au chargement du classeur.
    */
    function onOpen() {
    const ui = SpreadsheetApp.getUi();
    ui.createMenu(‹ 📅 Agenda ›)
    .addItem(‹ ➕ Ajouter un événement ›, ‹ openSidebar ›)
    .addToUi();
    ui.createMenu(‹ 📤 Formulaire ›)
    .addItem(‹ ✉️ Envoyer formulaire dispos ›, ‹ genererFormulaireIntelligent ›)
    .addItem(‹ 🗑️ Nettoyer événements passés ›, ‹ supprimerEvenementsPasses ›)
    .addToUi();

importerTousLesEvenementsDansStaff();
configureStaffSheet();
}

/**

  • Ouvre la sidebar Agenda
    */
    function openSidebar() {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const villes = […new Set(
    ss.getSheetByName(‹ Villes ›)
    .getRange(‹ A2:A › + ss.getSheetByName(‹ Villes ›).getLastRow())
    .getValues().flat().filter(String)
    )];
    const events = […new Set(
    ss.getSheetByName(‹ BaseÉvénements ›)
    .getRange(‹ D2:D › + ss.getSheetByName(‹ BaseÉvénements ›).getLastRow())
    .getValues().flat().filter(String)
    )];
    const tpl = HtmlService.createTemplateFromFile(‹ Sidebar ›);
    tpl.villesList = villes;
    tpl.eventsList = events;
    SpreadsheetApp.getUi()
    .showSidebar(tpl.evaluate().setTitle(‹ 📅 Agenda ›).setWidth(350));
    }

/**

  • Gère la soumission de la sidebar et remplit BaseÉvénements, Événements et Staff
    */
    function setSidebarData(start, end, ville, evt, typeVal) {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const base = ss.getSheetByName(‹ BaseÉvénements ›);
    const plan = ss.getSheetByName(‹ Événements ›);
    const staff = ss.getSheetByName(‹ Staff ›);
    if (!base || !plan || !staff) return;

ss.toast(Ajout: ${evt}, ‹ Agenda ›, 3);

// 1) Charger Staff J→O en map
const staffRows = staff.getRange(2,10,staff.getLastRow()-1,6).getValues();
const staffMap = {};
staffRows.forEach(r => {
const [evtName, city, startTime, hoursBefore, cutoffTime, nbPersons] = r;
if (evtName) staffMap[evtName] = { city, startTime, hoursBefore, cutoffTime, nbPersons };
});
const staffCfg = staffMap[evt] || {};
const cfg = loadCreneauxConfig();
const tz = Session.getScriptTimeZone();
const fmt = ‹ EEEE d MMMM ›;

// 2) BaseÉvénements : mois de start
const moisFr = [‹ janvier ›,‹ février ›,‹ mars ›,‹ avril ›,‹ mai ›,‹ juin ›,‹ juillet ›,‹ août ›,‹ septembre ›,‹ octobre ›,‹ novembre ›,‹ décembre ›];
const startDate = new Date(start);
const monthName = moisFr[startDate.getMonth()];
base.insertRows(2);
base.getRange(2,3).clearDataValidations();
base.getRange(2,1,1,8).setValues([[
‹  ›, // A
monthName, // B
typeVal, // C
evt, // D
ville, // E
‹  ›, // F
startDate, // G
new Date(end) // H
]]);
applyTypeMapping(2);

// 3) Générer créneaux selon Staff J→O
const d0 = new Date(start), d1 = new Date(end);
for (let d = new Date(d0); d <= d1; d.setDate(d.getDate()+1)) {
const dayLabel = d.toLocaleDateString(‹ fr-FR ›, { weekday: ‹ long ›, day: ‹ numeric ›, month: ‹ long › });
const isFirst = d.getTime() === d0.getTime();

// déterminer horaires
const st = staffCfg.startTime
  ? Utilities.formatDate(new Date(staffCfg.startTime), tz, 'HH:mm')
  : Utilities.formatDate(startDate, tz, 'HH:mm');
const cut = staffCfg.cutoffTime
  ? Utilities.formatDate(new Date(staffCfg.cutoffTime), tz, 'HH:mm')
  : Utilities.formatDate(
      new Date(d0).setHours(d0.getHours() - (staffCfg.hoursBefore || cfg[evt]?.hoursBefore || 0)),
      tz, 'HH:mm'
    );
const np = staffCfg.nbPersons || cfg[evt]?.personsNeeded || 1 || cfg[evt]?.personsNeeded || 1;

// a) Installation
if (isFirst) {
  plan.appendRow([
  dayLabel,          // A
  typeVal,           // B
  ville,             // C
  `Installation de ${cut} à ${st}`,  // D
  '', '', '', '', '', '', '', '',     // E-H: col5-12 blanks
  `0/${np}`,        // I: col13 status
  '',               // J: col14
  ''                // K: col15
]);
  staff.appendRow([ dayLabel, evt, '', np ]);
}
// b) Journée
if (typeVal === 'TERRASSE ÉPHÉMÈRE') {
  plan.appendRow([
  dayLabel,          // A
  typeVal,           // B
  ville,             // C
  `Journée de ${cut} à ${st}`,       // D
  '', '', '', '', '', '', '', '',     // E-H: col5-12 blanks
  '0/1',            // I: col13 status
  '',               // J: col14
  ''                // K: col15
]);
  staff.appendRow([ dayLabel, evt, '', 1 ]);
}
// c) Événement
plan.appendRow([
  dayLabel,          // A
  typeVal,           // B
  ville,             // C
  `Événement à ${st}`,              // D
  '', '', '', '', '', '', '', '',    // E-H: col5-12 blanks
  `0/${np}`,        // I: col13 status
  '',               // J: col14
  ''                // K: col15
]);
staff.appendRow([ dayLabel, evt, '', np ]);

}

// 4) Re-configurer statuts et dropdowns de dispo

updateStatusColumn();
updateAvailabilityDropdowns();
// (ne pas réappliquer updateStaffDropdowns pour éviter liste complète)
configureStaffSheet();
}

/**

  • Génère et envoie le formulaire de disponibilités,
  • et inscrit le numéro en colonne O de la feuille Événements.
    */
    function genererFormulaireIntelligent() {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const props = PropertiesService.getScriptProperties();

// Purge des anciens déclencheurs handleFormSubmit
ScriptApp.getProjectTriggers()
.filter(t => t.getHandlerFunction() === ‹ handleFormSubmit ›)
.forEach(t => ScriptApp.deleteTrigger(t));

// Incrément du compteur
let count = parseInt(props.getProperty(‹ formCount ›) || ‹ 0 ›, 10) + 1;
props.setProperty(‹ formCount ›, String(count));

// Création du formulaire
const title = 'Dispos à remplir – Formulaire ’ + count;
const form = FormApp.create(title);
ScriptApp.newTrigger(‹ handleFormSubmit ›)
.forForm(form)
.onFormSubmit()
.create();

// Prénom et e-mail
form.addListItem()
.setTitle(‹ Quel est ton prénom ? ›)
.setChoiceValues(getStaffList());
form.addTextItem()
.setTitle(‹ Quelle est ton adresse e-mail ? ›)
.setRequired(true);

// Récupérer créneaux par date
const sh = ss.getSheetByName(‹ Événements ›);
const data = sh.getDataRange().getValues();
const today = new Date(); today.setHours(0,0,0,0);
const datesMap = new Map();
data.slice(1).forEach(r => {
const date = r[0], slot = r[3], formId = r[14];
if (!(date instanceof Date) || date < today || formId) return;
const ds = Utilities.formatDate(date, ss.getSpreadsheetTimeZone(), ‹ EEEE d MMMM yyyy ›);
if (!datesMap.has(ds)) datesMap.set(ds, );
datesMap.get(ds).push(String(slot).trim());
});
datesMap.forEach((slots, ds) => {
form.addCheckboxItem()
.setTitle(ds)
.setChoiceValues([…new Set(slots)]);
});

// Envoi du formulaire
const url = form.getPublishedUrl() + ‹ ?form= › + count;
MailApp.sendEmail(
Session.getActiveUser().getEmail(),
title,
'Merci de répondre : ’ + url
);

// Inscrire le compteur en colonne O
data.slice(1).forEach((r,i) => {
if (r[0] instanceof Date && r[0] >= today && !r[14]) {
sh.getRange(i + 2, 15).setValue(count);
}
});

supprimerEvenementsPasses();
}

/**

  • Traite la soumission du formulaire
    */
    function handleFormSubmit(e) {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const staff = ss.getSheetByName(‹ Staff ›);
    const resp = e.response.getItemResponses();
    let prenom = ‹  ›, email = ‹  ›;
    const selections = ;
    resp.forEach(ir => {
    const title = ir.getItem().getTitle();
    const ans = ir.getResponse();
    if (title === ‹ Quel est ton prénom ? ›) prenom = ans;
    else if (title === ‹ Quelle est ton adresse e-mail ? ›) email = ans;
    else (ans || ).forEach(slot => selections.push({ date: title, slot }));
    });
    if (!prenom || !email) return;

// Mise à jour du staff
const names = staff.getRange(‹ G2:G ›).getValues().flat().filter(Boolean);
if (!names.includes(prenom)) {
staff.appendRow([‹  ›,‹  ›,‹  ›,‹  ›,‹  ›,‹  ›,prenom,email]);
} else {
const idx = names.indexOf(prenom) + 2;
staff.getRange(idx, 8).setValue(email);
}

// Enregistrer les dispos en A/B/C
const colA = staff.getRange(‹ A2:A ›).getValues().flat();
selections.forEach(sel => {
const row = colA.findIndex(v => !v) + 2;
staff.getRange(row,1,1,3).setValues([[prenom, sel.date, sel.slot]]);
colA[row - 2] = prenom;
});

updateStatusColumn();
updateAvailabilityDropdowns();
}

/**

  • Importe tous les événements uniques dans Staff
    */
    function importerTousLesEvenementsDansStaff() {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const base = ss.getSheetByName(‹ BaseÉvénements ›);
    const staff = ss.getSheetByName(‹ Staff ›);
    if (!base || !staff) return;
    const events = base.getRange(‹ D2:D › + base.getLastRow()).getValues().flat().filter(String);
    const uniques = Array.from(new Set(events)).sort();
    const colJ = staff.getRange(‹ J2:J › + staff.getLastRow()).getValues().flat();
    uniques.forEach(evt => {
    if (!colJ.includes(evt)) {
    const idx = colJ.findIndex(v => !v);
    if (idx >= 0) staff.getRange(idx + 2, 10).setValue(evt);
    }
    });
    }

/**

  • Configure les colonnes J→O de Staff
    */
    function configureStaffSheet() {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const staff = ss.getSheetByName(‹ Staff ›);
    const base = ss.getSheetByName(‹ BaseÉvénements ›);
    if (!staff || !base) return;
    const last = staff.getLastRow(); if (last < 2) return;
    const raw = base.getRange(2, 3, base.getLastRow() - 1, 3).getValues();
    const map = raw.filter(r => r[1]).map(r => ({ event: r[1], city: r[2], type: r[0] }));

// Col J : dropdown événements
const evts = […new Set(map.map(o => o.event))].sort();
staff.getRange(2, 10, last - 1)
.setDataValidation(
SpreadsheetApp.newDataValidation()
.requireValueInList(evts, true)
.build()
);

// Col L : dropdown heures HH:mm
const rawTimes = staff.getRange(‹ E22:E47 ›).getValues().flat();
const timesList = rawTimes
.map(t => t instanceof Date
? Utilities.formatDate(t, ss.getSpreadsheetTimeZone(), ‹ HH:mm ›)
: String(t)
)
.filter(s => s && s !== ‹ 0:00 ›);
staff.getRange(2, 12, last - 1)
.setDataValidation(
SpreadsheetApp.newDataValidation()
.requireValueInList(timesList, true)
.build()
);

const cfg = loadCreneauxConfig();
const tz = ss.getSpreadsheetTimeZone();

for (let i = 2; i <= last; i++) {
const evtType = staff.getRange(i, 10).getValue();
const be = map.find(o => o.event === evtType);
if (!be) continue;

// Col K : ville
staff.getRange(i, 11).setValue(be.city || '');

// Col M : heures avant
if (!staff.getRange(i, 13).getValue()) {
  const h = (be.type === 'BUVETTE' || be.type === 'TERRASSE ÉPHÉMÈRE') ? 3
          : (be.type === 'MARCHE' ? 2 : (cfg[evtType]?.hoursBefore || 1));
  staff.getRange(i, 13).setValue(h);
}

// Col N : heure début - M
const Lval = staff.getRange(i, 12).getValue();
const Mval = Number(staff.getRange(i, 13).getValue());
if (Lval) {
  const str = Lval instanceof Date
    ? Utilities.formatDate(Lval, tz, 'HH:mm')
    : Lval;
  const [h, m] = String(str).split(':').map(Number);
  const d = new Date(0, 0, 0, h, m);
  d.setHours(d.getHours() - Mval);
  staff.getRange(i, 14).setValue(Utilities.formatDate(d, tz, 'HH:mm'));
}

// Col O : nombre de personnes
if (!staff.getRange(i, 15).getValue()) {
  const p = (be.type === 'BUVETTE' || be.type === 'TERRASSE ÉPHÉMÈRE') ? 4
          : (be.type === 'MARCHE' ? 1 : (cfg[evtType]?.personsNeeded || 1));
  staff.getRange(i, 15).setValue(p);
}

}
}

function updateStatusColumn() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const evtSh = ss.getSheetByName(‹ Événements ›);
const staff = ss.getSheetByName(‹ Staff ›);
if (!evtSh || !staff) return;

const staffData = staff.getRange(2, 10, staff.getLastRow() - 1, 6).getValues();
const neededMap = {};
staffData.forEach(r => { if (r[0]) neededMap[r[0]] = Number(r[5]) || 0; });
const cfg = loadCreneauxConfig();

const rows = evtSh.getDataRange().getValues().slice(1);
rows.forEach((r, i) => {
const slot = r[3];
const type = r[1];
let needed;
if (typeof slot === ‹ string › && slot.startsWith(‹ Installation ›)) {
if (type === ‹ BUVETTE › || type === ‹ TERRASSE ÉPHÉMÈRE ›) needed = 3;
else if (type === ‹ MARCHE ›) needed = 1;
else needed = cfg[type]?.personsNeeded || 1;
} else if (typeof slot === ‹ string › && slot.startsWith(‹ Événement ›)) {
needed = neededMap[type] || 0;
} else if (typeof slot === ‹ string › && slot.startsWith(‹ Journée ›)) {
needed = (type === ‹ TERRASSE ÉPHÉMÈRE › ? 1 : cfg[type]?.personsNeeded || 1);
} else {
needed = cfg[type]?.personsNeeded || 1;
}
const assigned = needed > 0 ? r.slice(4, 4 + needed).filter(Boolean).length : 0;
evtSh.getRange(i + 2, 13).setValue(${assigned}/${needed});
});
}

function updateAvailabilityDropdowns() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const evtSh = ss.getSheetByName(‹ Événements ›);
if (!evtSh) return;
const staffData = ss.getSheetByName(‹ Staff ›).getDataRange().getValues().slice(1);

// colonnes slots D’après E→L (8 colonnes maxi)
const startCol = 5; // colonne E
const maxCols = 8;
const data = evtSh.getDataRange().getValues().slice(1);

data.forEach((r, i) => {
const rowIndex = i + 2;
// 1) vider validations et contenus sur E→L
evtSh.getRange(rowIndex, startCol, 1, maxCols)
.clearDataValidations()
.clearContent();

// 2) ne poser dropdown que sur needed cases
const needed = parseInt(String(r[12]).split('/')[1], 10) || 0;
if (needed > 0) {
  const date = r[0];
  const slot = r[3];
  // filtrer staff ayant coché
  const avail = staffData
    .filter(s => s[1] === date && s[2] === slot)
    .map(s => s[0]);
  const rule = SpreadsheetApp.newDataValidation()
    .requireValueInList(avail, true)
    .build();
  // appliquer sur E→E+needed-1
  evtSh.getRange(rowIndex, startCol, 1, needed)
    .setDataValidation(rule);
}

});
}
/**

  • Met à jour statut assigned/needed dans Événements
    */
    function updateStatusColumn() {
    // … unchanged …
    }

/**

  • Met à jour dropdowns dispos dans Événements
    */
    function updateAvailabilityDropdowns() {
    // … unchanged …
    }

/**

  • onEdit → recalcul de N (col 14) si L (12) ou M (13) changent,
  • puis mise à jour Événements si O (15) change.
    */
    function onEdit(e) {
    const sh = e.range.getSheet();
    if (sh.getName() !== ‹ Staff ›) return;
    const col = e.range.getColumn();
    const row = e.range.getRow();
    if (row < 2) return;
    const tz = SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone();

// recalcul heure démarrage - M
if (col === 12 || col === 13) {
const Lval = sh.getRange(row,12).getValue();
const Mval = Number(sh.getRange(row,13).getValue())||0;
const str = Lval instanceof Date ? Utilities.formatDate(Lval,tz,‹ HH:mm ›) : Lval;
const [h,m] = String(str).split(‹ : ›).map(Number);
if (!isNaN(h) && !isNaN(m)) {
const d = new Date(0,0,0,h,m);
d.setHours(d.getHours() - Mval);
sh.getRange(row,14).setValue(Utilities.formatDate(d,tz,‹ HH:mm ›));
}
}

// si nombre de personnes change → mise à jour Événements
if (col === 15) {
updateStatusColumn();
updateAvailabilityDropdowns();
}
}

/**

  • Nettoyage BaseÉvénements : clear G/H si date fin passée
    */
    function cleanBaseEventsDates() {
    const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(‹ BaseÉvénements ›);
    if (!sh) return;
    const today = new Date(), last = sh.getLastRow();
    if (last < 2) return;
    sh.getRange(2,7,last-1,2).getValues().forEach((r,i) => {
    if (r[1] instanceof Date && r[1] < today) {
    sh.getRange(i+2,7,1,2).clearContent();
    }
    });
    }

/**

  • Supprime événements passés de Événements
    */
    function supprimerEvenementsPasses() {
    const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(‹ Événements ›);
    if (!sh) return;
    const data = sh.getDataRange().getValues();
    const today = new Date();
    const kept = [data[0]];
    data.slice(1).forEach(r => {
    if (!(r[0] instanceof Date) || r[0] >= today) kept.push(r);
    });
    sh.clearContents();
    sh.getRange(1,1,kept.length,kept[0].length).setValues(kept);
    }

/**

  • Charge configuration des créneaux
    */
    function loadCreneauxConfig() {
    const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(‹ CreneauxConfig ›);
    if (!sh) return {};
    return Object.fromEntries(
    sh.getRange(‹ A2:C › + sh.getLastRow()).getValues()
    .filter(r => r[0])
    .map(([evt,h,p]) => [evt,{hoursBefore:h,personsNeeded:p}])
    );
    }

/**

  • Récupère les prénoms en G2:G de Staff
    */
    function getStaffList() {
    const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(‹ Staff ›);
    return sh ? sh.getRange(‹ G2:G ›).getValues().flat().filter(Boolean) : ;
    }