Kernprobleem
Één methode, te veel stappen
Seizoensprijzen, belasting en korting zaten verstrengeld in één grote berekening — moeilijk te testen, moeilijk uit te leggen.
Case study · prijslogica · boekingsplatform
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 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
Analyse
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
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
Basisprijs ophalen
Één query, één keer — niet in de lus
Seizoensprijzen overlappen
Per nacht: rate?.AdjustedPrice ?? basePrice
Tax apart berekenen
total += total × taxRate
Korting toepassen
Pas ná tax — expliciete volgorde
totaalprijs = som(nightlyRates) + tax − korting
Elke variabele is traceerbaar naar één stap.
Trade-off
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
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
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
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
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
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.

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