Case study · prijslogica · boekingsplatform

Boekingsprijzen die kloppen én uitlegbaar zijn

Een prijs die afhangt van datum, seizoen en kortingscode is niet ingewikkeld — zolang je de stappen niet door elkaar gooit. Deze case gaat over hoe we die scheiding aanbrachten in een bestaande prijsservice, zodat elke uitkomst traceerbaar werd.

Kernprobleem

Één methode, te veel stappen

Seizoensprijzen, belasting en korting zaten verstrengeld in één grote berekening — moeilijk te testen, moeilijk uit te leggen.

Betrouwbaarheid

Per stap controleerbaar

De prijs is nu stap voor stap te volgen: van basisbedrag via seizoensregel en korting tot eindtotaal — ook voor niet-technische stakeholders.

Resultaat

Voorspelbaar & uitlegbaar

De prijs kan nu per stap worden gevolgd en uitgelegd — ook door mensen buiten het ontwikkelteam.

Probleem

Een prijs die klopt maar niet uitgelegd kan worden

Een gast boekt drie nachten. Eén nacht valt in een seizoensperiode, de andere niet. Er komt BTW bij, en soms een kortingscode. De prijs klopt — net niet.

Te hoog en de klant haakt af. Te laag en de marge verdwijnt. En als niemand kan uitleggen hoe de uitkomst tot stand kwam, is dat een eerlijk probleem.

Wat er concreet misliep

  • De basisprijs werd soms opnieuw opgehaald midden in de berekening.
  • Seizoensprijzen en standaardprijzen stonden door elkaar.
  • Belasting en korting werden niet als aparte stappen behandeld.

Analyse

De oorzaak was niet de formule — het was de structuur

De basisprijs werd soms opnieuw uit de database gehaald midden in de berekening.

Seizoensprijzen en standaardprijzen stonden door elkaar, zonder duidelijke prioriteit.

Belasting en korting werden niet als aparte stappen behandeld, maar door de uitkomst gemengd.

Eén uitzondering in de prijsregel — zoals een kortingscode die bijna verlopen was — was genoeg om de logica te laten ontsporen.

Eén methode deed te veel. Zolang dat zo was, bleef elk nieuw prijsscenario een potentieel risico.

Oplossing

Opgesplitst in stappen — elk met één verantwoordelijkheid

Eerst de vaste gegevens ophalen, daarna per nacht de juiste prijs bepalen, dan tax, dan korting. Die volgorde is nu expliciet — niet impliciet verstopt in één grote methode.

Stap 1 — Basisprijs één keer ophalen

var basePrice = await _context.Houses
    .Where(h => h.Id == house.Id)
    .Select(h => h.Price)
    .FirstAsync(cancellationToken);

Stap 2 — Seizoensprijzen in één query

var seasonalRates = await _context.Houses
    .Where(h => h.Id == house.Id)
    .SelectMany(h => h.SeasonalRates)
    .Where(sr => sr.StartDate <= endDate
              && sr.EndDate >= startDate)
    .ToListAsync(cancellationToken);

Stap 3 & 4 — Totaal, tax en korting

decimal total = 0;

for (var date = startDate; date < endDate; date = date.AddDays(1))
{
    var rate = seasonalRates
        .FirstOrDefault(r => r.StartDate <= date
                           && r.EndDate >= date);

    total += rate?.AdjustedPrice ?? basePrice;
}

// Stap 3: tax apart
total += total * taxRate;

// Stap 4: korting apart
if (discountCode is { IsValid: true })
    total -= discountCode.Amount;

Prijspijplijn

Vier stappen, elk met één verantwoordelijkheid

1

Basisprijs ophalen

Één query, één keer — niet in de lus

2

Seizoensprijzen overlappen

Per nacht: rate?.AdjustedPrice ?? basePrice

3

Tax apart berekenen

total += total × taxRate

4

Korting toepassen

Pas ná tax — expliciete volgorde

totaalprijs = som(nightlyRates) + tax − korting

Elke variabele is traceerbaar naar één stap.

Trade-off

Meer code, maar veel minder risico

Pro

Elke stap is onafhankelijk testbaar. Een prijsfout is direct traceerbaar naar één stap — niet ergens verstopt in een gecombineerde methode.

Con

Iets meer code en een expliciete volgorde die je moet bewaken. Bij een simpele vaste prijs zonder uitzonderingen is dit een overkill.

Architectuurprincipe

Prijslogica hoort niet in een controller of één grote methode

Zodra een prijs afhangt van meer dan één variabele — datum, seizoen, klanttype, promotiecode — wordt één grote methode een verzamelplaats van verborgen afhankelijkheden.

Ontwerpstandpunt

Schrijf de berekening altijd expliciet uit. Ja, dat vraagt iets meer code. Het voordeel is dat je prijzen kunt uitleggen, testen en aanpassen zonder dat iedereen moet gokken wat de bedoeling was.

Resultaat

Minder chaos, meer controle over elke boeking

De prijs is stap voor stap uitlegbaar — ook naar niet-technische stakeholders.

Kans op verborgen prijsfouten door gedeelde mutable state is geëlimineerd.

Een nieuwe seizoensregel toevoegen raakt slechts één geïsoleerde stap.

Onnodige herhaalde databaseopvragingen in een lus zijn vermeden.

Lessons learned

Wat ik meeneem voor elk systeem met dynamische prijzen

Prijslogica is altijd gevoeliger dan die er op het eerste gezicht uitziet. Niet omdat de wiskunde moeilijk is, maar omdat de uitzonderingen zich stapelen.

Houd prijs, tax en korting expliciet gescheiden als aparte stappen.

Haal vaste gegevens één keer op vóór de lus — nooit erin.

Test altijd met een boeking van meerdere nachten die een seizoensovergang bevat.

Test een kortingscode die geldig is én één die net buiten de geldigheidsperiode valt.

Een prijs die je niet kunt uitleggen, kun je ook niet vertrouwen.

Duidelijke mening

Expliciet uitschrijven in plaats van "even snel" door elkaar

Voor teams die boekingen, facturatie of dynamische prijzen bouwen: ik zou deze logica altijd bewust uitschrijven. Niet omdat het indruk maakt, maar omdat een prijs die je niet kunt uitleggen ook een prijs is die je niet kunt vertrouwen.

Per nacht

Seizoensprijs waar van toepassing, anders basisprijs

Tax na subtotaal

Na sommering, vóór kortingstoepassing

Korting als laatste stap

Pas wanneer alle andere componenten vaststaan

Takeaway

Als een prijs afhangt van datum, seizoen en korting, maak de berekening dan klein en stap voor stap.

Een prijs moet niet alleen kloppen. Hij moet ook uitlegbaar zijn — aan de klant, aan de boekhouding, en aan de developer die er over zes maanden iets aan moet aanpassen.

Veelgestelde vragen over prijslogica in boekingssoftware

Waarom is een aparte stap voor belasting zo belangrijk?
Belasting wordt berekend op de netto prijs na korting, of soms net ervoor — afhankelijk van de jurisdictie. Als je tax door de loop mengt, verlies je de controle over die volgorde. Een aparte stap maakt de volgorde expliciet en testbaar.
Wat als de seizoensprijzen overlappen?
Dan moet er een duidelijke prioriteitsregel zijn: de meest specifieke of de meest recente wint. Door de seizoensprijzen geïsoleerd op te halen en te evalueren, kun je die regel op één plek definiëren en testen — los van de rest van de berekening.
Is dit patroon ook toepasbaar buiten boekingen?
Ja. Overal waar een prijs afhangt van meerdere variabelen — tijdstip, klanttype, promotiecode, volume — loont het om de berekening op te splitsen. Facturatie, abonnementen, kortingssystemen: het patroon is hetzelfde.
Hoeveel extra code vraagt deze aanpak?
Iets meer dan een "alles-in-één" methode, maar niet veel. Het grootste verschil zit niet in de hoeveelheid code, maar in de structuur: elke stap heeft één verantwoordelijkheid, wat unit testing drastisch vereenvoudigt.
Kan dit ook helpen als ik de prijzen aan klanten of boekhouding moet verantwoorden?
Precies dat is het voornaamste praktische voordeel. Als elke stap benoembaar is — basisprijs, seizoenstoeslag, BTW, korting — kun je een prijsoverzicht genereren dat een klant meteen begrijpt.
Mitch

Heb jij ook prijslogica die werkt maar waarbij niemand precies meer weet hoe?

Plan een vrijblijvende digitale kennismaking met Mitch en ontdek wat wij voor jouw organisatie kunnen betekenen.