Een verlofuren app bouwen

In deze blog post gaan we een app maken. Je kunt zelf meedoen in een Developer Edition of Trailhead Playground.

Verlofuren

Bij de meeste banen krijg je vakantiedagen. Vaak zijn dat in Nederland 5 weken. 4 weken zijn wettelijk bepaald en veel werkgevers doen daar een week bovenwettelijk verlof bovenop. Wettelijk verlof mag je inzetten t/m 30 juni van het volgende kalenderjaar. Bovenwettelijk verlof heeft een houdbaarheidsdatum van 5 jaar. Bij sommige bedrijven en functies bestaat ook de mogelijkheid om met gemaakte overuren extra verlof op te sparen.

Natuurlijk bestaan er al vele apps waarmee HR afdelingen dit verloftegoed en verbruik kunnen bijhouden, maar laten we er toch een bouwen als oefening.

User Stories

De eerste stap wanneer je een app als deze gaat maken is de een lijst maken met vereisten voor de gebruikers. Deze kun je gebruiken om te controleren of wat je maakt in al die behoeften voorziet. Deze schrijf je vanuit het perspectief van de eindgebruiker. We noemen dit user stories en er is een vast stramien van hoe je ze schrijft. Er staat in vanuit welke rol de wens komt en wat de meerwaarde uiteindelijk oplevert voor gebruikers in die rol. Voor dit project denk ik onder andere aan deze stories:

  1. Als medewerker wil ik zelf mijn verlofdagen kunnen indienen en mijn beschikbaar verloftegoed kunnen inzien, opdat ik weet hoeveel verlof ik nog kan opnemen.
  2. Als medewerker wil ik verloftegoed dat het eerst vervalt als eerste opmaken, opdat ik niet onnodig verloftegoed verlies.
  3. Als HR administrateur wil ik dat de juiste hoeveelheid verloftegoed automatisch wordt aangemaakt wanneer een nieuwe medewerker start, opdat het niet vergeten kan worden en altijd correct berekend is.
  4. Als HR administrateur wil ik dat nieuw verloftegoed automatisch beschikbaar wordt gemaakt voor medewerkers wanneer een nieuw boekjaar start, opdat het niet vergeten kan worden en altijd correct berekend is.
  5. Als HR administrateur wil ik dat verlofaanvragen automatisch worden afgewezen wanneer het verloftegoed van de medewerker niet toereikend is voor de aangevraagde uren.

Natuurlijk kunnen we nog wel wat meer van deze stories bedenken, maar laten we met met de eerste twee beginnen. Hiermee kunnen we al een redelijke basis voor de app leggen.

Datamodel

Als we een app gaan maken zullen we eerst moeten vaststellen wat voor datamodel we nodig hebben. Met het datamodel bedoel ik de verschillende objecten, velden op die objecten en de relaties tussen de objecten.

Employee

We zouden ook het standaard Contact object kunnen gebruiken, maar dan moet je ook gaan nadenken over hoe je de zichtbaarheid van die contacten en vooral hoe je hun verlofadministratie privé houdt. Contacten zijn normaal immers voor de meeste gebruikers toegankelijk en worden doorgaans juist voor klanten gebruikt. Hierom maken we een custom Employee object.

Absence Credits

Het tweede object is het verloftegoed. Ik configureer alles in de eerste plaats in het Engels en vertaal waar nodig naar Nederlands. Ik wil verschillende verloftegoeden aan een medewerker kunnen toekennen omdat er verschillende vervaldata gelden.

Deze velden hebben we nodig op het Absence Credit Object;

  • Toegekende uren: het aantal uren (getal, 2 cijfers achter de komma) dat oorspronkelijk is toegekend. Dit veld noem ik Credited Hours.
  • Vervaldatum: de laatste dag waarop de medewerker het verloftegoed kan gebruiken. Dit veld noem ik Valid Untill.
  • Omschrijving, bijvoorbeeld “Wettelijk Verlof 2022”. Veldnaam: Description
  • Lookup relatie naar Employee.

Absence Consumptions

Aan de andere kant wordt verlof opgenomen. Op deze records hebben we de volgende gegevens nodig:

  • Consumption Date, de datum waarop het verlof wordt gebruikt
  • Hours Consumed, het aantal uren verlof dat wordt ingezet
  • Description, waarmee de medewerker voor zichzelf kan noteren waar het verlof aan besteed wordt
  • Status
    • Requested (aangevraagd)
    • Cancelled (geannuleerd)
    • Approved (goedgekeurd)
    • Denied (afgewezen)
    • Consumed (verbruikt)

Absence Credit Consumptions

Met dit junction object koppelen we verbruik van verlof aan een verloftegoed. Dit heeft een belangrijk voordeel ten opzichte van de Consumptions onder het Absence Credit hangen: We kunnen verlof voor één dag uit verschillende verloftegoeden halen. Bijvoorbeeld als de medewerker nog maar 6 uur Wettelijk Verlof 2022 heeft en een hele dag van 8 uur opneemt, kan één Absence Credit Consumption worden gemaakt voor 6 uur die aan het Absence Credit record voor Wettelijk Verlof 2022 gekoppeld is en één voor 2 uur die aan Bovenwettelijk Verlof 2022 gekoppeld is.

Het Absence Credit Consumption object heeft deze velden nodig:

  • Used Hours (getalveld, 2 cijfers achter de komma, zoals we doen met alle velden die uren vertegenwoordigen)
  • Master-Detail relatie naar Absence Credit
  • Master-Details relatie naar Absence Consumption

Ik gebruik hier Master-Detail relaties omdat ik roll up summaries wil gebruiken. Daar komen we zo op. Ons datamodel ziet er nu zo uit

Roll up Summaries

Roll up summaries zijn velden die een samenvatting kunnen geven van meerdere onderliggende records.

Absence Credit – Used Hours

Op het Absence Credit object maken we een roll up summary veld dat de Used Hours van alle onderliggende Absence Credit Consumptions bij elkaar optelt.

Nu kunnen we ook een formuleveld Remaining Hours maken dat laat zien hoeveel verloftegoed er nog over is.

in de formule zie je dat ik meteen heb meegenomen dat het resterend tegoed sowieso op 0 wordt gesteld wanneer de laatste datum van geldigheid van dit verloftegoed in het verleden is.

Absence Consumption – Hours Covered

Op Absence Consumption maken we ook een roll up summary van Used Hours op alle onderliggende Absence Credit Consumptions. Hier vertegenwaardigt dat het aantal uren van deze verlofdag dat ook daadwerkelijk op een verloftegoed is afgeboekt.

En vervolgens maken we een formule voor Hours Not Covered.

Automatisering

Nu het datamodel staat, kijken we naar hoe we er automatisering op kunnen toepassen. Laten we ons richten op de vereiste dat opgenomen verlof van het tegoed met de vroegste vervaldatum moet worden afgeboekt. Hiervoor gaan we een flow maken die een (of meer) Absence Credit Consumption record(s) aanmaakt die aan de juiste Absence Credit gekoppeld wordt.

Absence Credit Consumptions moeten ontstaan wanneer de Status van een Absence Consumption verandert van Requested of Approved naar Consumed. Dus daarvoor maken we een record triggered flow. In de Entry Conditions filter ik bewust op Status is changed in plaats van Status equals Consumed. Ik wil dezelfde record triggered flow namelijk ook gebruiken om de Absence Credit Consumptions weer te verwijderen wanneer de status naar Canceled gaat.

We plaatsen in de flow dus een beslissing om de nieuwe status vast te stellen

De verdere logica plaatsen we in een Autolaunched flow die we vanuit deze Record Triggered flow zullen starten. Hierdoor blijft de Record Triggered flow overzichtelijker, is de subflow gemakkelijker te debuggen en zou je deze vanuit verschillende record triggered flows of zelfs handmatig via een action of screen flow kunnen opstarten.

Beschikbare Absence Credits ophalen

Eerst halen we dus de Absence Credit records op die aan deze filters voldoen:

  • Gerelateerd aan dezelfde Employee als de Absence Consumption die de flow triggert
  • Remaining Hours > 0
  • Valid Untill >= {!$Flow.CurrentDate}

Om te zorgen dat we de record met de vroegste vervaldatum als eerste in die lijst krijgen, sorteren we oplopend op Valid Untill datum.

We halen alle beschikbare Absence Credits op, want mogelijk hebben we er meer dan één nodig om alle Hours Consumed af te boeken op een verloftegoed. Door meteen alle Absence Credits op te halen, hoeven we niet later opnieuw records op te halen.

Loop

Dat we een verzameling Absence Credits hebben, betekent ook dat we een loop nodig hebben. We willen niet voor elke iteratie in de loop het totaal aantal gebruikte verlofuren afboeken, maar wel op het juiste totaal uitkomen. Daarom gaan we in de flow bijhouden hoeveel van de Hours Consumed we nog niet geboekt hebben. Dat doen we met een eenvoudige number variable. Laten we die hoursNotCovered noemen. Hierin slaan we aan het begin van de flow, dus vóór de loop start het volledige aantal te boeken uren op.

Wanneer we een Absence Credit Consumption aanmaken trekken we de uren die we daar op boeken van dat getal af.

Direct na het begin van de loop plaatsen we een beslissing, waarin we toetsen of hoursNotCovered groter dan 0 is.

Zo ja, dan vervolgen we de weg waarin we een Absence Credit Consumption record gaan aanmaken.

Zo nee, dan zijn er geen nieuwe Absence Credit Consumptions nodig en kunnen we direct terugkeren naar het loop element. Mochten er nog een paar Absence Credits meer zijn, dan zal er voor elk daarvan niets meer gebeuren in de loop. Maar we zullen de hele loop wel moeten afmaken. Verlaat je de loop zonder steeds terug te keren naar het loop element, dan kan het zijn dat je records gaat missen die er wel hadden moeten zijn.

Als het aantal hoursNotCovered kleiner dan of gelijk is aan de Remaining Hours van de gevonden Absence Credit, volstaat het aanmaken van één Absence Credit Consumption record. In dat geval zal Hours Used op de Absence Credit Consumption gelijk zijn aan het totale aantal uren dat geboekt moest worden.

Als het aantal hoursNotCovered groter is dan de Remaining Hours, maken we een Absence Credit Consumption gerelateerd aan deze Absence Credit voor hetzelfde aantal uren als de Remaining Hours. We gebruiken dan de laatste resterende uren van deze Absence Credit en kunnen we dus niet alle gebruikte uren uit één verloftegoed halen. Dit secnario is precies waarom de loop nodig is.

Hiervoor gebruiken we weer een beslissing en afhankelijk van de uitkomst de toewijzing waarin we alle hoursNotCovered gebruiken als Used Hours voor de Absence Credit Consumption of juist de Remaining Hours van de Absence Credit waarop we willen boeken.

Assignment wanneer Remaining Hours van huidige Absence Credit voldoende zijn om alle uren van de Absence Consumption te dekken
Assignment wanneer de Remaining Hours op de huidige Absence Credit onvoldoende zijn om alle uren van de Absence Consumption te dekken

Absence Credit Consumption voor een deel van het gebruikte verlof

In dit laatste geval zijn dus niet alle Used Hours op een verloftegoed afgeboekt en moeten we dus nóg een Absence Credit Consumption maken. We moeten de flow dus laten onthouden hoeveel uren er op die tweede Absence Credit Consumption nog moeten worden gebruikt. Dat doen we door het zojuist geboekte aantal uren van hoursConsumed af te trekken. Dus als je van 8 uur 5 uur op het eerste verloftegoed kunt boeken, moet je nog 3 uur op het tweede verloftegoed boeken.

Record toevoegen aan Record Collection

Omdat er één Absence Credit Consumption record is voorbereid, moeten we die nu aan een Record Collection toevoegen. Records aanmaken doe je immers altijd na het afronden van de loop en nooit binnen de loop. Je slaat dan de Record Collection in één keer op in de database in plaats van een Create records voor elke afzonderlijke record te gebruiken.

Bij de volgende iteratie van de loop, met het volgende beschikbare verloftegoed, wordt weer gecheckt of hoursConsumed groter dan 0 is. Zo ja, dan verloopt de loop opnieuw zoals zojuist beschreven. Is hoursConsumed wél 0, dan keer je vanaf die beslissing direct terug naar het loop element en gebeurt er in deze iteratie niets. Dat herhaalt zich tot de opgehaalde Absence Credits allemaal voorbij gekomen zijn en dan is de loop echt ten einde.

De Absence Credit Consumptions daadwerkelijk aanmaken

Na het voltooien van de laatste iteratie van de loop, kunnen we Absence Credit Consumption records die we binnen de loop met Assignments voorbereid hebben, daadwerkelijk gaan aanmaken met een Create Records element voor de gehele collectie AbsenceCreditConsumptions in één keer.

Bonus

Deze flow werkt en kan dit ook ruim binnen de limieten, maar dat je misschien een paar iteraties van de loop hebt, waarvan je eigenlijk al kunt vaststellen dat ze niet nodig zijn, is toch een beetje jammer.

Kunnen we de loop niet laten ophouden zodra we alle gebruikte verlofuren op een tegoed hebben afgeboekt?

Ja, natuurlijk. De simpele manier is om voor de voor de route waarbij je alle hoursNotCovered kunt boeken op één Absence Credit, na het toevoegen van je Absence Credit Consumption aan de collectie de loop uit te gaan zonder nog een keer langs het loop element te gaan. Zal in dit geval best werken, maar ik heb ervaring met flows die niet goed werken als je op zo’n ‘niet helemaal nette’ manier de loop verlaat. Het is beter om je gewoonte aan te wennen om het altijd netjes te doen. Maar dan hebben we wel een iets minder algemeen bekend trucje nodig.

De loop heeft een iteratie voor alle records in de collectie Get_Absence_Hours_Credits. Wat ik hier doe, omdat ik weet dat ik dat ik de volgende iteraties niet meer nodig heb, is alle volgende records na de huidige uit die collectie verwijderen. Dat heeft direct na die Assignment effect en dus zitten er geen volgende Credits meer in de collectie en eindigt de loop hierna dus.

Op deze pagina op de help site van Salesforce kun je uitvinden wat deze ‘Remove After First’ en andere soortgelijke operators nu precies doen.

En nu tijd om zelf verder te spelen

Maak een Employee, een paar Credits en wat Consumptions aan en kijk wat er gebeurt. Probeer ook uit hoe je de andere User Stories kunt realiseren. Misschien bedenk je zelf nog aanvullende user stories. Dit soort oefeningen die iets verder gaan dan de lessen op Trailhead (zeker ook omdat je ze zelf bedenkt) is erg fijn oefenmateriaal. Veel plezier en succes hiermee.