# Een goedkeuringsflow bouwen met Apps Script, Forms en Gmail Een goedkeuringsflow bouwen met Apps Script, Forms en Gmail is een uitstekend voorbeeld van een complete bedrijfsautomatisering. Een medewerker dient een aanvraag in, de leidinggevende krijgt een mail met een goedkeur- en een afwijsknop, en de uitkomst wordt automatisch vastgelegd en teruggekoppeld. In dit artikel zet je zo'n flow stap voor stap op. [[TOC]] ## De opzet van de flow De flow bestaat uit vier delen. Eerst dient iemand een aanvraag in via een Google Form. Die inzending logt in een spreadsheet met een unieke id en de status In behandeling. Daarna krijgt de goedkeurder een mail met twee links die naar jouw webapp wijzen, een voor goedkeuren en een voor afwijzen. Wanneer de goedkeurder klikt, werkt de webapp de status bij en mailt de aanvrager de uitkomst. :::info title="Webapp als beslispunt" De goedkeur- en afwijslinks wijzen naar je `doGet`-webapp met parameters, bijvoorbeeld `?id=123&actie=goedkeuren`. De webapp leest die parameters via `e.parameter`. De referentie staat op developers.google.com/apps-script/guides/web. ::: ## Stap 1: de aanvraag loggen Met een onFormSubmit-trigger schrijf je elke aanvraag naar een blad met een unieke id: ```javascript function bijAanvraag(e) { const blad = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Aanvragen'); const response = e.response; const aanvrager = response.getRespondentEmail(); const items = response.getItemResponses(); const omschrijving = items[0].getResponse(); const id = Utilities.getUuid(); blad.appendRow([id, aanvrager, omschrijving, 'In behandeling', new Date()]); stuurNaarGoedkeurder(id, aanvrager, omschrijving); } ``` `Utilities.getUuid()` geeft een unieke id zodat elke aanvraag eenduidig terug te vinden is. Koppel de `bijAanvraag`-functie aan een installeerbare onFormSubmit-trigger, niet aan een simpele trigger, want alleen een installeerbare trigger mag mailen en de respondent uitlezen. ## Stap 2: de goedkeurder mailen met beslislinks Bouw twee links naar je webapp en stuur ze naar de goedkeurder: ```javascript function stuurNaarGoedkeurder(id, aanvrager, omschrijving) { const basis = 'jouw-webapp-url'; const goedkeuren = basis + '?id=' + id + '&actie=goedkeuren'; const afwijzen = basis + '?id=' + id + '&actie=afwijzen'; const html = '

Nieuwe aanvraag van ' + aanvrager + ':

' + '

' + omschrijving + '

' + '

Goedkeuren | ' + 'Afwijzen

'; GmailApp.sendEmail('manager@cloud-captains.com', 'Aanvraag ter goedkeuring', 'Open in HTML.', { htmlBody: html }); } ``` De webapp-URL krijg je pas na de eerste deploy (zie stap 3 en het stappenplan). Zet hem daarna in `basis`. ## Stap 3: de beslissing verwerken in de webapp De webapp leest de parameters, controleert ze en werkt de status bij: ```javascript function doGet(e) { const id = e.parameter.id; const actie = e.parameter.actie; if (!id || (actie !== 'goedkeuren' && actie !== 'afwijzen')) { return HtmlService.createHtmlOutput('Ongeldige aanvraag.'); } const blad = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Aanvragen'); const data = blad.getDataRange().getValues(); for (let i = 1; i < data.length; i++) { if (data[i][0] === id) { if (data[i][3] !== 'In behandeling') { return HtmlService.createHtmlOutput('Deze aanvraag is al verwerkt.'); } const status = actie === 'goedkeuren' ? 'Goedgekeurd' : 'Afgewezen'; blad.getRange(i + 1, 4).setValue(status); informeerAanvrager(data[i][1], status); return HtmlService.createHtmlOutput('Aanvraag ' + status.toLowerCase() + '.'); } } return HtmlService.createHtmlOutput('Aanvraag niet gevonden.'); } ``` :::danger title="Valideer elke klik streng" Controleer in de webapp altijd of de aanvraag nog In behandeling is voordat je de status wijzigt. Zonder die check kan iemand een oude link hergebruiken of een al verwerkte aanvraag opnieuw veranderen. Valideer de parameters streng en sta alleen geldige acties toe, want de webapp-URL is gokbaar. ::: ## Stap 4: de aanvrager terugkoppelen Tot slot informeer je de aanvrager over de uitkomst: ```javascript function informeerAanvrager(email, status) { MailApp.sendEmail(email, 'Beslissing op je aanvraag', 'Je aanvraag is ' + status.toLowerCase() + '.'); } ``` :::warn title="Voorkom dubbele verwerking bij gelijktijdige klikken" Twee snelle klikken kunnen elkaar overlappen voordat de status is weggeschreven. Gebruik `LockService.getScriptLock()` rond het lezen en schrijven van de rij, zodat slechts een klik per aanvraag de status mag wijzigen. Zo blijft de statuscheck betrouwbaar, ook bij gelijktijdige aanvragen. ::: :::howto title="De goedkeuringsflow opzetten" 1. Maak een Google Form met e-mailverzameling aan en koppel een spreadsheet via **Antwoorden**. 2. Schrijf de `bijAanvraag`-functie die logt en de goedkeurder mailt, en koppel een installeerbare onFormSubmit-trigger. 3. Bouw de `doGet`-webapp die acties valideert en de status bijwerkt. 4. Klik op **Implementeren**, kies **Nieuwe implementatie** en daarna type **Web-app**, en zet de URL in je mailfunctie. 5. Test de hele keten met een proefaanvraag van begin tot eind. ::: :::faq ### Hoe voorkom ik dat iemand zonder rechten goedkeurt? Zet de webapp op toegang binnen je organisatie en laat hem uitvoeren als de toegankende gebruiker. Controleer in `doGet` de waarde van `Session.getActiveUser().getEmail()` tegen een lijst van toegestane goedkeurders en weiger elke andere klik. ### Wat als de goedkeurder twee keer klikt? De statuscheck In behandeling vangt dit op: bij de tweede klik is de status al gewijzigd en toont de webapp dat de aanvraag al verwerkt is. Wikkel het lezen en schrijven in een `LockService`-slot om ook gelijktijdige klikken af te vangen. ### Kan ik meerdere goedkeurders in serie hebben? Ja. Voeg een statuskolom per niveau toe en stuur na de eerste goedkeuring een mail naar de volgende goedkeurder voordat je definitief afrondt. ### Hoe maak ik de beslislinks veiliger? Voeg naast de id een ondertekend token toe dat je in de spreadsheet bewaart en in `doGet` vergelijkt, zodat een geraden id alleen niet volstaat. Een token genereer je bijvoorbeeld met `Utilities.computeHmacSha256Signature` over de id plus een geheim. ### Waarom zie ik geen mail nadat ik het formulier invul? Vrijwel altijd ontbreekt de installeerbare onFormSubmit-trigger of zijn de machtigingen nog niet geaccepteerd. Voer de functie eerst een keer handmatig uit zodat je de toegang goedkeurt, en controleer daarna in de triggerlijst of de trigger echt is gekoppeld. ### Hoeveel mails mag ik per dag versturen? Apps Script kent een dagelijkse maillimiet die afhangt van je accounttype, lager voor gratis accounts en hoger voor Workspace-abonnementen. Lees de actuele limiet uit met `MailApp.getRemainingDailyQuota()` en bouw een terugvalpad als die op nul staat. ::: ## Volgende stap Deze flow bouwt voort op [[apps-script-formulier-response|formulierreacties verwerken]] en [[apps-script-webhook|webapps]]. Beveilig hem verder met de adviezen uit [[apps-script-best-practices|best practices]].