Business Digital

Automatiser un flux PME avec Apps Script : tutoriel pas-à-pas

8 دقائق للقراءة

📍 Article principal : Google Workspace pour PME : architecture et gouvernance
Ce tutoriel construit une automatisation Apps Script de bout en bout : depuis une ligne de Google Sheets, génération d’un PDF à partir d’un modèle Docs, envoi par mail au destinataire, mise à jour du statut.

Objectif

Automatiser un flux Sheets → Docs → PDF → Gmail dans Apps Script avec runtime V8, déclencheurs installables et menu personnalisé Sheets. Code testé sur runtime V8 (mai 2026), API SpreadsheetApp, DocumentApp, DriveApp, GmailApp.

Quotas Apps Script (référence officielle)

Source : Quotas for Google Services (Apps Script) — dernière mise à jour 20 avril 2026.

  • Durée d’exécution : 6 minutes par appel (Consumer comme Workspace).
  • Triggers : 20 par utilisateur et par script.
  • URL Fetch : 20 000 / jour Consumer, 100 000 / jour Workspace.
  • MailApp recipients : 100 / jour Consumer, 1 500 / jour Workspace.
  • Réinitialisation 24 heures après la première requête.

Runtime

Rhino déprécié depuis le 20 février 2025, exécution arrêtée le 31 janvier 2026 (source officielle Google for Developers). Tous les projets tournent sur le runtime V8. Vérification : Paramètres du projet → Activer le runtime Chrome V8.

Prérequis

  • Compte Google Workspace Business Standard ou supérieur (quotas plus larges).
  • Connaissances JavaScript de base (boucle, condition, fonction).
  • Un Google Sheet et un Google Docs (modèle), tous deux dans un drive partagé.
  • Un dossier Drive (id récupéré depuis l’URL) pour archiver les PDF.
  • Niveau : intermédiaire.
  • Temps : 90 minutes.

Étape 1 — Schéma fixe de la feuille

Créer un Sheet Devis 2026 avec en ligne 1 les en-têtes :

A: Date | B: N° devis | C: Client | D: Email client | E: Description | F: Montant HT | G: Statut | H: Lien PDF

Le script lit par index de colonne. Toute insertion de colonne au milieu casse les références — figer le schéma dès le départ.

Étape 2 — Projet Apps Script lié

Depuis le Sheet : Extensions → Apps Script. Renommer le projet Automation Devis. Cocher Paramètres du projet → Afficher le fichier de manifeste appsscript.json. Vérifier Activer le runtime Chrome V8.

Étape 3 — Modèle Docs

Créer un Google Docs Modele Devis avec balises de fusion :

DEVIS N° {{NUMERO}}
Date : {{DATE}}

Client : {{CLIENT}}

Description :
{{DESCRIPTION}}

Montant HT : {{MONTANT}} EUR
TVA 20 % : {{TVA}} EUR
Montant TTC : {{TTC}} EUR

Récupérer l’ID du Docs depuis l’URL (segment entre /d/ et /edit).

Étape 4 — Code de génération PDF

Dans Code.gs, code de la fonction qui copie le modèle, remplace les balises, exporte en PDF :

const TEMPLATE_DOC_ID = '1abcDEF...';   // Id du Docs modèle
const ARCHIVE_FOLDER_ID = '1xyzGHI...'; // Id du dossier Drive cible

function genererPdfDevis(d) {
  const modele = DriveApp.getFileById(TEMPLATE_DOC_ID);
  const copie = modele.makeCopy(`Devis_${d.numero}_${d.client}`);
  const doc = DocumentApp.openById(copie.getId());
  const corps = doc.getBody();

  const tva = d.montant * 0.20;
  const ttc = d.montant * 1.20;

  corps.replaceText('{{NUMERO}}', d.numero);
  corps.replaceText('{{DATE}}', d.date);
  corps.replaceText('{{CLIENT}}', d.client);
  corps.replaceText('{{DESCRIPTION}}', d.description);
  corps.replaceText('{{MONTANT}}', d.montant.toFixed(2));
  corps.replaceText('{{TVA}}', tva.toFixed(2));
  corps.replaceText('{{TTC}}', ttc.toFixed(2));

  doc.saveAndClose();

  const pdfBlob = copie.getAs('application/pdf');
  const dossier = DriveApp.getFolderById(ARCHIVE_FOLDER_ID);
  const pdfFile = dossier.createFile(pdfBlob).setName(`Devis_${d.numero}.pdf`);

  copie.setTrashed(true); // Met le Docs intermédiaire à la corbeille, conserve le PDF
  return pdfFile;
}

Note : setTrashed(true) envoie en corbeille le Docs intermédiaire (réversible 30 jours). Le fichier PDF reste dans le dossier d’archive.

Étape 5 — Test isolé

function testGenerationPdf() {
  const donnees = {
    numero: 'DEV-TEST-001',
    date: '04/05/2026',
    client: 'Acme SARL',
    description: 'Audit de sécurité Workspace — 2 jours',
    montant: 1500
  };
  const pdf = genererPdfDevis(donnees);
  Logger.log('PDF généré : ' + pdf.getUrl());
}

Sauvegarder, sélectionner testGenerationPdf dans le sélecteur de fonction, cliquer Exécuter. Au premier appel, autoriser les scopes demandés (Drive, Docs). Sortie attendue dans le panneau d’exécution : ligne PDF généré : https://drive.google.com/file/d/…. Ouvrir l’URL pour vérifier le PDF rempli.

Si une balise n’est pas remplacée : vérifier que le Docs contient bien {{NUMERO}} sans espaces ({ {NUMERO} } ou {{ NUMERO }} ne matchent pas).

Étape 6 — Envoi par mail

function envoyerDevisParMail(d, pdfFile) {
  const objet = `Votre devis ${d.numero}`;
  const corps =
    `Bonjour ${d.client},

Veuillez trouver ci-joint le devis ${d.numero} d'un montant de ${d.montant.toFixed(2)} EUR HT.

Cordialement.`;

  GmailApp.sendEmail(d.email, objet, corps, {
    attachments: [pdfFile.getBlob()],
    name: 'Service Commercial'
  });
}

Quota MailApp Workspace : 1 500 destinataires / 24 h. Pour des volumes supérieurs, basculer sur Gmail API (quota par utilisateur plus généreux, à voir dans la documentation officielle).

Étape 7 — Menu personnalisé sur le Sheet

Le déclencheur simple onOpen installe automatiquement un menu à l’ouverture du Sheet :

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Devis')
    .addItem('Envoyer le devis sélectionné', 'envoyerDevisLigneActive')
    .addToUi();
}

function envoyerDevisLigneActive() {
  const ui = SpreadsheetApp.getUi();
  const feuille = SpreadsheetApp.getActiveSheet();
  const ligne = feuille.getActiveRange().getRow();

  if (ligne === 1) {
    ui.alert('Sélectionnez une ligne de devis (pas l’en-tête).');
    return;
  }

  const v = feuille.getRange(ligne, 1, 1, 8).getValues()[0];
  const donnees = {
    date: Utilities.formatDate(v[0], Session.getScriptTimeZone(), 'dd/MM/yyyy'),
    numero: v[1],
    client: v[2],
    email: v[3],
    description: v[4],
    montant: parseFloat(v[5])
  };

  const pdf = genererPdfDevis(donnees);
  envoyerDevisParMail(donnees, pdf);

  feuille.getRange(ligne, 7).setValue('Envoyé');
  feuille.getRange(ligne, 8).setValue(pdf.getUrl());
  ui.alert(`Devis ${donnees.numero} envoyé à ${donnees.email}`);
}

Sauvegarder, fermer/rouvrir le Sheet : le menu Devis apparaît à droite de Aide.

Étape 8 — Manifeste OAuth explicite

Dans appsscript.json :

{
  "timeZone": "Africa/Dakar",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets.currentonly",
    "https://www.googleapis.com/auth/documents",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/script.send_mail",
    "https://www.googleapis.com/auth/script.container.ui"
  ]
}

Adapter timeZone. La déclaration explicite des scopes rend l’autorisation prévisible et auditable lors d’un partage du script à d’autres utilisateurs.

Étape 9 — Trigger temporel pour relances

Pour balayer le Sheet une fois par jour et relancer les devis envoyés depuis plus de 7 jours, ajouter une fonction et la planifier via Déclencheurs dans l’éditeur (icône horloge) :

function rappelDevisNonRepondus() {
  const feuille = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  const donnees = feuille.getDataRange().getValues();
  const seuil = new Date();
  seuil.setDate(seuil.getDate() - 7);

  for (let i = 1; i < donnees.length; i++) {
    const dateDevis = donnees[i][0];
    const statut = donnees[i][6];
    const email = donnees[i][3];

    if (statut === 'Envoyé' && dateDevis instanceof Date && dateDevis < seuil && email) {
      GmailApp.sendEmail(email, `Rappel devis ${donnees[i][1]}`,
        `Bonjour, nous revenons vers vous concernant le devis ${donnees[i][1]} envoyé le ${Utilities.formatDate(dateDevis, Session.getScriptTimeZone(), 'dd/MM/yyyy')}.`);
      feuille.getRange(i + 1, 7).setValue('Relancé');
    }
  }
}

Dans Déclencheurs → Ajouter un déclencheur : fonction rappelDevisNonRepondus, source d’événement Déclenchement temporel, type Quotidien, plage horaire de votre choix.

Erreurs fréquentes

Erreur Cause Solution
Exécution timeout 6 min Trop de lignes en un appel Découper en lots avec PropertiesService et trigger récurrent
Authorization required Premier appel d’un nouveau scope Exécuter la fonction manuellement pour autoriser
Balises non remplacées Espaces parasites dans le Docs ({ {NUMERO} }) Vérifier la casse exacte sans espaces
Mails en spam SPF/DKIM non configurés Configurer côté tenant (cf. tutoriel de setup, étape 8)
Quota MailApp dépassé Plus de 1 500 destinataires/24 h Workspace : attendre 24 h ou basculer sur Gmail API
Runtime Rhino actif Projet ancien Bascule V8 dans Paramètres du projet

Tutoriels associés

Références officielles

FAQ

Coût d’Apps Script ?
Inclus dans tous les plans Workspace, gratuit pour les comptes Consumer, sans coût additionnel. Les seules limites sont les quotas publiés.

Exécution sans utilisateur connecté ?
Possible via les déclencheurs temporels, qui exécutent en arrière-plan au nom du créateur du trigger.

Dépassement des 6 minutes ?
Pattern documenté : sauvegarde de la position courante via PropertiesService, exit volontaire avant 5 min 30, trigger qui reprend à la position sauvegardée.

Test sans envoi de vrais mails ?
Remplacer GmailApp.sendEmail par GmailApp.createDraft qui crée un brouillon dans la boîte sans l’envoyer.

Versionnement du code ?
L’éditeur intègre un historique des versions. Pour Git, utiliser clasp, l’outil officiel CLI Apps Script qui synchronise un projet avec un dépôt local.

Apps Script peut-il appeler une API externe ?
Oui via UrlFetchApp.fetch(), soumis au quota URL Fetch (100 000 / jour Workspace).

مشاركة