Hallo mede Php-ers,

Sinds enkele dagen ben ik bezig met een ingewikkelde (althans ik vind het ingewikkeld) renteberekeningsclass. De berekeningen zijn redelijk nauwkeurig en komen voor wat betreft de enkelvoudige rente en meervoudige contractuele rente exact overeen met de online tool die meestal voor dergelijke renteberekeningen wordt gebruikt: http://www.wettelijkerente.net/renteberekening.aspx

De wettelijke handelsrente komt nog niet helemaal overeen met voornoemde online bereken tool en dat betekent dat OF mijn formule onjuist is OF de online rekentool. Uiteraard zou ik graag willen weten of jullie zien waar dit aan ligt en of er verbeterpunten zijn (ongetwijfeld).

Hieronder de 2 classes:
<?php
public function checkJaarGrens($vervalDatum,$beginDatum,$eindDatum,$numOfYears){
// Controleer of de periode jaargrens bevat
for($i = 1; $i < $numOfYears + 1; $i++)
{
if(
($beginDatum < ($vervalDatum + ($i*60*60*24*365))) &&
($eindDatum > ($vervalDatum + ($i*60*60*24*365))) )
{
return $vervalDatum + ($i*60*60*24*365);
}
}
}

// Bereken rente
public function calcRente($renteSoort,$samengesteld){

// Gegevens factuur ophalen
$db = new PDO('mysql:host=localhost;port=3307;dbname=****','****','****');
$sql = "SELECT
UNIX_TIMESTAMP(factuurDatum) AS factuurDatum,
factuurBedrag,betaalTermijn,renteSamengesteld, rentePercentage
FROM Invoice WHERE id = '".$this->_id."'";
$result = $db->query($sql);
foreach($result as $row)
{
// Gegevens verzamelen
$heden = time();
$factuurBedrag = $row['factuurBedrag'];
$rentePercentage = $row['rentePercentage'];
$samengesteld = $row['renteSamengesteld'];
$factuurDatum = $row['factuurDatum']; // Datum in seconden
$vervalDatum = $factuurDatum + ($row['betaalTermijn']*24*60*60) + (60*60*24);
$rentePeriode = $heden - $vervalDatum;
$subtotaal = $factuurBedrag;
$subtotaalRente = 0;


echo '<br /><br />';
echo 'De begindatum van de factuur is '.$row['factuurDatum'].'<br />';
echo 'De betaaltermijn is '.$row['betaalTermijn'].' dagen.<br />';
echo 'Dit zijn '.$row['betaalTermijn']*24*60*60 .' seconden.<br />';
echo 'Dit betekent een vervalDatum per '.$vervalDatum.'<br />';
echo 'Dit betekent een vervalDatum per '.date('Y-m-d',$vervalDatum).' tot aan
'.date('Y-m-d',$heden).'<br />';
echo 'De rentePeriode is dus '.$rentePeriode.' seconden of '.ceil($rentePeriode/24/60/60) .' dagen';
echo '<br /><br />';

}

if($renteSoort == 1) // == Wettelijke handelsrente
{

if($samengesteld == 1)
{
// Bereken wettelijke rente (samengesteld)
echo 'De wettelijke rente (samengesteld) wordt berekend.<br />';

$db = new PDO('mysql:host=localhost;port=3307;dbname=****','****','****');
$sql = "SELECT
UNIX_TIMESTAMP(eindDatum) AS eindDatum,
UNIX_TIMESTAMP(beginDatum) AS beginDatum,
rentePercentage
FROM wettelijkerente
WHERE renteSoort = 1
ORDER BY beginDatum ASC;
";
$result = $db->query($sql);
$aantalJaar = floor(($heden - $vervalDatum)/60/60/24/365);
echo 'Er zijn '.$aantalJaar.' jaren<br />';
foreach($result as $row){

$beginDatum = $row['beginDatum'];
$eindDatum = $row['eindDatum'];
$rentePercentage = $row['rentePercentage'];

// Periodes afbakenen
if(
($beginDatum < $vervalDatum) && ($eindDatum > $vervalDatum) && ($eindDatum <= $heden) ||
($beginDatum < $vervalDatum) && ($eindDatum > $vervalDatum) && ($eindDatum >= $heden) ||
($beginDatum > $vervalDatum) && ($eindDatum > $vervalDatum) && ($eindDatum <= $heden) ||
($beginDatum > $vervalDatum) && ($eindDatum > $vervalDatum) && ($eindDatum >= $heden)
)
{
echo 'De begindatum: '.date('d-m-Y',$beginDatum).'<br />';
echo 'De einddatum: '.date('d-m-Y',$eindDatum).'<br />';
echo 'Het rentepercentage: '.$rentePercentage.'<br />';

// ==> Eerste periode
if( ($beginDatum < $vervalDatum) && ($eindDatum > $vervalDatum) && ($eindDatum < $heden) )
{
echo 'Deze periode is gebroken aan het begin.<br />';

// Controleer of de periode jaargrens bevat
if($this->checkJaarGrens($vervalDatum,$beginDatum,$eindDatum,$aantalJaar) > 0)
{

// Ja, er is een jaargrens binnen deze periode
echo 'Deze periode bevat een jaargrens<br />';

$jaarGrens = $this->checkJaarGrens($vervalDatum,$vervalDatum,$eindDatum,$aantalJaar);

echo 'Deze periode heeft '. ceil(($eindDatum-$vervalDatum)/60/60/24) .' dagen<br />';

// ==> Eerste gedeelte rente

// Aantal dagen voor de jaargrens
$aantalDagen = ceil(($jaarGrens - $vervalDatum)/60/60/24);
echo 'Aantal dagen in de periode voor de jaargrens: '.$aantalDagen.'<br />';

// Rente voor de jaargrens berekenen
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;

echo 'Oud subtotaal = '.$subtotaal.'<br />';
echo 'Rente deze periode: '.$renteBedrag.'<br />';
echo 'Rente vorige periodes: '.$subtotaalRente.'<br />';
echo 'Subtotaal rente: '.$renteBedrag + $subtotaalRente.'<br />';

// Oude rente (vorige periodes) en nieuwe rente bij elkaar optellen
$subtotaalRente = $subtotaalRente + $renteBedrag;
echo 'Subtotaal rente: '.$subtotaalRente.'<br />';

// ==> Nieuw subtotaal berekenen
// Nieuwe rente tot nu toe optellen bij subtotaal
$subtotaal = $subtotaal + $subtotaalRente;

echo 'Nieuw subtotaal = '.$subtotaal.'<br />';

// subtotaalRente resetten (want reeds bij subtotaal opgeteld)
$subtotaalRente = 0;

// ==> Twee gedeelte rente
// Aantal dagen voor de jaargrens
$aantalDagen = ceil(($eindDatum - $jaarGrens)/60/60/24);
echo 'Aantal dagen in de periode na de jaargrens: '.$aantalDagen.'<br />';

// Rente na de jaargrens berekenen
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;
$subtotaalRente = $subtotaalRente + $renteBedrag;

echo 'Nieuwe rente deze periode: '.$renteBedrag.'<br />';
echo 'Rente subtotaal: '.$subtotaalRente.'<br />';

}else{

// Nee, er is geen jaargrens binnen deze periode
echo 'Deze periode bevat geen jaargrens.<br />';

// Aantal dagen berekenen
$aantalDagen = ceil(($eindDatum - $vervalDatum)/60/60/24);
echo 'Aantal dagen in deze periode: '.$aantalDagen.'<br />';

echo 'Subtotaal: '.$subtotaal.'<br />';

// Rente berekenen en toevoegen aan renteSubtotaal
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;
echo 'Rentebedrag deze periode: '.$renteBedrag.'<br />';

$subtotaalRente = $subtotaalRente + $renteBedrag;
echo 'Rente subtotaal: '.$subtotaalRente.'<br />';

}
}
// ==> Laatste periode
elseif( (($beginDatum < $vervalDatum) && ($eindDatum > $heden)) ||
(($beginDatum > $vervalDatum) && ($eindDatum > $heden)) )
{
echo 'Deze periode is gebroken aan het einde<br />';

// Controleer of de periode jaargrens bevat
if($this->checkJaarGrens($vervalDatum,$beginDatum,$heden,$aantalJaar) > 0)
{

// Ja, er is een jaargrens binnen deze periode
echo 'Deze periode bevat een jaargrens<br />';

$jaarGrens = $this->checkJaarGrens($vervalDatum,$beginDatum,$heden,$aantalJaar);

echo 'Deze periode heeft '. ceil(($eindDatum-$vervalDatum)/60/60/24) .' dagen<br />';

// ==> Eerste gedeelte rente

// Aantal dagen voor de jaargrens
$aantalDagen = ceil(($jaarGrens - $vervalDatum)/60/60/24);
echo 'Aantal dagen in de periode voor de jaargrens: '.$aantalDagen.'<br />';

// Rente voor de jaargrens berekenen
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;

echo 'Oud subtotaal = '.$subtotaal.'<br />';
echo 'Rente deze periode: '.$renteBedrag.'<br />';
echo 'Rente vorige periodes: '.$subtotaalRente.'<br />';
echo 'Subtotaal rente: '.$renteBedrag + $subtotaalRente.'<br />';

// Oude rente (vorige periodes) en nieuwe rente bij elkaar optellen
$subtotaalRente = $subtotaalRente + $renteBedrag;
echo 'Subtotaal rente: '.$subtotaalRente.'<br />';

// ==> Nieuw subtotaal berekenen
// Nieuwe rente tot nu toe optellen bij subtotaal
$subtotaal = $subtotaal + $subtotaalRente;

echo 'Nieuw subtotaal = '.$subtotaal.'<br />';

// subtotaalRente resetten (want reeds bij subtotaal opgeteld)
$subtotaalRente = 0;

// ==> Twee gedeelte rente
// Aantal dagen voor de jaargrens
$aantalDagen = ceil(($heden - $jaarGrens)/60/60/24);
echo 'Aantal dagen in de periode na de jaargrens: '.$aantalDagen.'<br />';

// Rente na de jaargrens berekenen
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;
$subtotaalRente = $subtotaalRente + $renteBedrag;

echo 'Nieuwe rente deze periode: '.$renteBedrag.'<br />';
echo 'Rente subtotaal: '.$subtotaalRente.'<br />';

}else{

// Nee, er is geen jaargrens binnen deze periode
echo 'Deze periode bevat geen jaargrens.<br />';

if($vervalDatum > $heden)
{
// Aantal dagen berekenen
$aantalDagen = ceil(($heden - $vervalDatum)/60/60/24);
}
else
{
// Aantal dagen berekenen
$aantalDagen = ceil(($heden - $beginDatum)/60/60/24);
}

echo 'Aantal dagen in deze laatste periode: '.$aantalDagen.'<br />';

echo 'Subtotaal voor rente: '.$subtotaal.'<br />';

echo 'Rente subtotaal vorige periodes: '.$subtotaalRente.'<br />';

// Rente berekenen en toevoegen aan renteSubtotaal
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;
$subtotaalRente = $subtotaalRente + $renteBedrag;
echo 'Nieuwe rente deze periode: '.$renteBedrag.'<br />';
echo 'Rente subtotaal: '.$subtotaalRente.'<br />';

// Totaalbedrag berekenen
$subtotaal = $subtotaal + $subtotaalRente;
echo 'Totaalbedrag: '.$subtotaal.'<br />';

}
}
// ==> Tussenperiode
else
{
echo 'Deze periode is niet gebroken maar heel<br />';

// Controleer of de periode jaargrens bevat
if($this->checkJaarGrens($vervalDatum,$beginDatum,$eindDatum,$aantalJaar) > 0)
{

// Ja, er is een jaargrens binnen deze periode
echo 'Deze periode bevat een jaargrens<br />';

$jaarGrens = $this->checkJaarGrens($vervalDatum,$beginDatum,$eindDatum,$aantalJaar);

echo 'Deze periode heeft '. ceil(($eindDatum-$beginDatum)/60/60/24) .' dagen<br />';

// ==> Eerste gedeelte rente

// Aantal dagen voor de jaargrens
$aantalDagen = ceil(($jaarGrens - $beginDatum)/60/60/24);
echo 'Aantal dagen in de periode voor de jaargrens: '.$aantalDagen.'<br />';

// Rente voor de jaargrens berekenen
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;

echo 'Oud subtotaal = '.$subtotaal.'<br />';
echo 'Rente deze periode: '.$renteBedrag.'<br />';
echo 'Rente vorige periodes: '.$subtotaalRente.'<br />';
echo 'Subtotaal rente: '.$renteBedrag + $subtotaalRente.'<br />';

// Oude rente (vorige periodes) en nieuwe rente bij elkaar optellen
$subtotaalRente = $subtotaalRente + $renteBedrag;
echo 'Subtotaal rente: '.$subtotaalRente.'<br />';

// ==> Nieuw subtotaal berekenen
// Nieuwe rente tot nu toe optellen bij subtotaal
$subtotaal = $subtotaal + $subtotaalRente;

echo 'Nieuw subtotaal = '.$subtotaal.'<br />';

// subtotaalRente resetten (want reeds bij subtotaal opgeteld)
$subtotaalRente = 0;

// ==> Twee gedeelte rente
// Aantal dagen voor de jaargrens
$aantalDagen = ceil(($eindDatum - $jaarGrens)/60/60/24);
echo 'Aantal dagen in de periode na de jaargrens: '.$aantalDagen.'<br />';

// Rente na de jaargrens berekenen
$renteBedrag = ($subtotaal * ($rentePercentage/100))/365*$aantalDagen;
$subtotaalRente = $subtotaalRente + $renteBedrag;

echo 'Nieuwe rente deze periode: '.$renteBedrag.'<br />';
echo 'Rente subtotaal: '.$subtotaalRente.'<br />';

}else{

// Nee, er is geen jaargrens binnen deze periode
echo 'Deze periode bevat geen jaargrens.<br />';

// Aantal dagen berekenen
$aantalDagen = ceil(($eindDatum - $beginDatum)/60/60/24);
echo 'Aantal dagen in deze periode: '.$aantalDagen.'<br />';

echo 'Subtotaal: '.$subtotaal.'<br />';

// Rente berekenen en toevoegen aan renteSubtotaal
$renteBedrag = ($factuurBedrag * ($rentePercentage/100))/365*$aantalDagen;
echo 'Rentebedrag deze periode: '.$renteBedrag.'<br />';

// Subtotaal rente
$subtotaalRente = $subtotaalRente + $renteBedrag;
echo 'Rentesubtotaal: '.$subtotaalRente.'<br />';

}
}


echo '<br />';
}

}

// Bereken de som van alle rentePeriodes

}
else
{
// Bereken wettelijke rente (enkelvoud)
return 'De wettelijke rente (enkelvoud) wordt berekend';
}

}elseif($renteSoort == 2) // == Wettelijke rente
{

if($samengesteld == 1)
{
// Bereken wettelijke handelsrente (samengesteld)
return 'De wettelijke handelsrente (samengesteld) wordt berekend';
}
else
{
// Bereken wettelijke handelsrente (enkelvoud)
return 'De wettelijke handelsrente (enkelvoud) wordt berekend';
}

}elseif($renteSoort == 3)
{

if($samengesteld == 1) // == Contractuele rente
{

// Bereken contractuele rente (samengesteld)
$bedrag = $factuurBedrag;
$rente = $rentePercentage;
$produkt = 100+$rente;
$rentePeriode = $heden - $vervalDatum;
$jaar = ceil($rentePeriode/24/60/60)/365;
for ($i = 1;$i<$jaar;$i++){
$bedrag = ($bedrag/100)*$produkt;
// echo '<p>Jaar ' . ($i) . ' - ' . $bedrag . '</p>';
$value[] = $bedrag;
}

// Nu dient nog het 'staartje' te worden toegevoegd.
$restPeriode = floor(($jaar-floor($jaar))*365-2);
$subtotaal = $value[floor($jaar)-1];
$restRente = $subtotaal*($rente/100)/365*$restPeriode;
$totaal = round($subtotaal+$restRente,2);
echo 'De contractuele rente (samengesteld) wordt berekend.<br />';
echo 'Resultaat = '.$totaal;
}
elseif($samengesteld == 2)
{
// Bereken contractuele handelsrente (enkelvoud)
$rentePeriode = $heden - $vervalDatum;
echo 'De contractuele rente (enkelvoud) wordt berekend.<br />';
echo 'Resultaat = ';
echo number_format($factuurBedrag * ($rentePercentage/100) / 365 * ceil($rentePeriode/24/60/60),2);
echo '<br />';
}
}
}

}
?>

Weergeven doe ik op de volgende wijze:
<?php

$_GET['dossier'] = '1';

// Instants van classes creeren
$invoice = new Invoice($_GET['dossier']);
$user = new User($invoice->getUser());
$debtor = new Debtor($invoice->getDebtor());
$dossier = new Dossier($invoice->getId());
$misc = new Misc();

// Factuurgegevens ophalen
$db = new PDO('mysql:host=localhost;port=3307;dbname=****','****','****');
$sql = "SELECT id,rentePercentage,renteSoort FROM invoice WHERE dossierId = ".$dossier->getId();
$result = $db->query($sql);
$i = 0;
foreach($result as $row) {
$obj = new Invoice($row['id']);
// Bereken rente
echo $obj->calcRente($row['renteSoort'],$row['rentePercentage']);
$i++;
}
unset($obj);
?>
Zou je alles in de code tags willen zetten?
[code*][*/code]
(de * wegdenken)

Bedankt!

Lars

EDIT: Heb je al gedaan :)
Zou je misschien de verschillen kunnen aangeven?
Let ook op, php is een weak typing taal. Dit betekend dus dat je niet per se de waarde kunt verwachten die jij verwacht.
Als jij 0 verwacht, zou dit nog best als 0.000001 kunnen opgeslagen worden.
Dit is eventueel te verhelpen met [php]type casting[/php] en [php]sprintf[/php].

Edit:
Natuurlijk is het wel handig om relevante code te plaatsen.
Edit2:
Waarom laat je je db niet lekker met die datum en tijd rekenen?
Edit3:
$subtotaal = $factuurBedrag;
Is die regel nodig?
Edit4:
$aantalJaar = floor(($heden - $vervalDatum)/60/60/24/365);
Een jaar is volgens Système international d'unités 3.15*10^7 seconde lang, een dag 86400 seconde. Dus dan is één jaar 364.5833333(...) dagen lang.
Karl Karl op 02/08/2010 14:05:49

Zou je misschien de verschillen kunnen aangeven?
Let ook op, php is een weak typing taal. Dit betekend dus dat je niet per se de waarde kunt verwachten die jij verwacht.
Als jij 0 verwacht, zou dit nog best als 0.000001 kunnen opgeslagen worden.
Dit is eventueel te verhelpen met [php]type casting[/php] en [php]sprintf[/php].
Het verschil is dat er een ander rentebedrag uitkomt. Bijvoorbeeld EUR 100,00 vanaf 02-01-2006 t/m 02-08-2010 levert bij mij 146,8694... op en met de online berekeningstool EUR 153,56

Karl Karl op 02/08/2010 14:05:49

Natuurlijk is het wel handig om relevante code te plaatsen.
Ik weet niet wat jij hierboven dan ziet, maar dat lijkt me toch redelijk relevant???

Karl Karl op 02/08/2010 14:05:49

Waarom laat je je db niet lekker met die datum en tijd rekenen?
Geen flauw idee. Kun je een voorbeeld geven?

Karl Karl op 02/08/2010 14:05:49

$subtotaal = $factuurBedrag;
Is die regel nodig?
Ja, aangezien ik de ene keer subtotaal nodig heb en de andere keer factuurBedrag en subtotaal telkens wordt overschreven.

Karl Karl op 02/08/2010 14:05:49

$aantalJaar = floor(($heden - $vervalDatum)/60/60/24/365);
Een jaar is volgens Système international d'unités 3.15*10^7 seconde lang, een dag 86400 seconde. Dus dan is één jaar 364.5833333(...) dagen lang.
Goede tip! Ga ik aanpassen.


Moet je echt zo ingewikkeld doen om de rente te berekenen?
8% rente per jaar?
0,022% per dag.
Totaalbedrag is beginbedrag × 1,00022 ^ dagen
In PHP:
<?php
$totaal = $begin * pow(1.00022, $dagen);
?>
Edit: typo
Peter aka Lekensteyn op 02/08/2010 14:15:18

Moet je echt zo ingewikkeld doen om de rente te berekenen?
8% rente per jaar?
0,022 per dag.
Totaalbedrag is beginbedrag × 1,022 ^ dagen
In PHP:
<?php
$totaal = $begin * pow(1.022, $dagen);
?>

Naja, misschien niet altijd nee. Maar zie je ook dat er een verschil is in samengestelde en enkelvoudige rente en dat jouw berekening daar geen rekening mee houdt? Maar ik kan hem wellicht gebruiken voor de enkelvoudige renteberekening. Dank u! Bovendien is jouw berekening niet volledig waardoor hij kleiner lijkt dan dat hij is want hoe kom jij bijvoorbeeld aan de rente per dag? ;-)
Ik ging van onderen naar boven kijken. Alleen die calcRente functie daar is eigenlijk interessant...
http://sqlzoo.net/howto/source/z.dir/i06dates.xml
is een voorbeeld van een datum tutorial.

Die if bij // Periodes afbakenen, volgens mij laat die alles door...
Dit is voor enkelvoudig, ik weet niet wat meervoudig inhoud, dus kan je er ook niet mee helpen.
Karl Karl op 02/08/2010 14:19:42

Ik ging van onderen naar boven kijken. Alleen die calcRente functie daar is eigenlijk interessant...
http://sqlzoo.net/howto/source/z.dir/i06dates.xml
is een voorbeeld van een datum tutorial.

Dank u, ga er meteen induiken.

Karl Karl op 02/08/2010 14:19:42

Die if bij // Periodes afbakenen, volgens mij laat die alles door...

Nope, hij laat bijvoorbeeld niet door wanneer de beginDatum en de eindDatum beiden kleiner zijn dan de vervalDatum. Maar wellicht is het dan handiger om dit om te draaien zit ik nu te denken. Moet voor de zekerheid even controleren of dit de enige is.
Weet je wel zeker dat die rente dat ding berekent correct is?
Als ik deze formules gebruik kom ik steeds rond de 136 uit.
Ja dat kan. De hoogte van de wettelijke rente verschilt van periode tot periode. Deze wordt namelijk ongeveer eens per half jaar gewijzigd. Voor een overzicht van de hoogte van de wettelijke rente per periode zie:
http://tarief.wettelijkerente.info/

Ik heb de tarieven dus opgenomen in een database table incasso.wettelijkerente

Reageren