fb pixel

Money, czyli co z tą kasą. Czym jest pieniądz w naszych aplikacjach?

|

Jest to jeden z tematów, które często wydają się oczywiste - czym jest pieniądz w naszych aplikacjach? No jak to czym, Decimal lub Float, liczba z najczęściej dwoma miejscami po przecinku. Proste, prawda? Właśnie nie do końca. Z pieniądzem w aplikacji wiążą się dwa problemy: zaokrąglenia oraz waluta.

Zaokrąglenia

Zazwyczaj przy pieniądzach operujemy na liczbie np. 10,54 oraz walucie np. PLN. Wspomniany problem zaokrąglania pojawia się jeśli do 10,54 musimy dodać kilka innych produktów, a każdy z nich ma inny VAT, który trzeba wyliczyć dla pojedynczych produktów oraz kwotę netto/brutto dla całego zamówienia. Musimy zaokrąglać tak, aby po X przeliczeniach nie zgubić groszy, bo dla nas to może niewiele, ale jak w specyficznym przypadku zniknie tu grosz, tam grosz, to księgowi mogą mieć potem spory problem. Jeśli końcowa kwota np. ma być podzielona na 30% przelew (zaliczka) i 70% gotówka na miejscu, to już robi się ciekawie, może się okazać, że nasza aplikacja daje inne wyniki niż system księgowy w firmie, którego raczej nie podejrzewam o przekłamania.

Waluta

Walutę zazwyczaj mamy definiowaną osobno, jest niezależnym polem/rekordem, możemy sprawdzić jaka jest, ale nie ma realnego powiązania z wartością/pieniądzem. Innymi słowy: operujemy na liczbach, nie walucie. Dzięki czemu musimy kontrolować czy dodawane/odejmowane kwoty mają tą samą walutę, ew. zrobić przeliczenie (wracają zaokrąglenia), co tylko jest kolejną rzeczą „muszę pamiętać o…”.

Problem istnieje nie od dziś, więc istnieją sprawdzone rozwiązania, które mogą nam ułatwić pracę z pieniędzmi. Martin Fowler w swojej książce „Architektura systemów zarządzania przedsiębiorstwem. Wzorce Projektowe” (brawurowe tłumaczenie „Patterns of Enterprise Applcation Architecture”) zaproponował jedno z nich, przedstawiając klasę Money typu ValueObject. Czym jest ValueObject? W dużym skrócie jest to niewielki obiekt trzymający dla nas jakąś wartość. Np. datę utworzenia i modyfikacji rekordu. W naszym przypadku będzie to kwota oraz waluta. Co nam to daje? Mamy pewność, że każda kwota ma przypisaną walutę, są ze sobą powiązane, to już nie jakieś tam osobne pole waluta, które zazwyczaj nas nie interesowało. Teraz jest integralną częścią obiektu Money, tak samo ważną jak kwota. Koniec z dodawaniem do kwoty liczby - dodajemy zawsze kwoty, czyli zawsze z walutą, dzięki czemu zawsze mamy kontrolę i pewność, że waluty się zgadzają (lub nie), trudniej o pomyłkę, łatwiej wyłapać błędy. Patrząc w kod od razu wiemy, że operujemy na pieniądzach, a nie ziarnkach grochu. Drugim plusem tego rozwiązania jest algorytm zaokrąglania. Już nie musimy się martwić o to czy wszystko dobrze policzyliśmy, mamy sprawdzony algorytm, który zrobi to za nas.

Problem nie jest nowy, rozwiązanie również, jest więc też biblioteka w PHP, która podejmuje temat w sposób w jaki pokazał nam Martin Fowler - Money. Kompatybilna z PSR-0 więc nie ma problemu z instalacją. Idea jest prosta, więc przykład (więcej w UnitTestach i dokumentacji) o co tutaj chodzi powinien wystarczyć aby zrozumieć jak ona działa. I tak:

$jim_price = $hannah_price = new Money(2500, new Euro);

Definiujemy dwie kwoty, dla Jima oraz Hannah, obie na 25EUR - tak, Money działa na liczbach całkowitych. Jak widać musimy podać walutę - w końcu to ValueObject Money, a nie liczba.

$coupon = new Money(500, new Euro);

//Tworzymy kupon rabatowy na 5EUR.

$jim_price->subtract($coupon);

//I odejmujemy go od kwoty Jima.

$jim_price->equals($hannah_price); // true

Powyższy przykład zwraca True, pomimo, że Jim dostał rabat. Wszystko przez ciągłość - Money zachowuje ciągłość zwracając zmienioną wartość, więc aby Jim miał rabat musimy przypisać wynik odejmowania do zmiennej.

$jim_price = $jim_price->subtract($coupon);
$jim_price->lessThan($hannah_price); // true
$jim_price->equals(Money::euro(2000)); // true

//I teraz Jim ma już odpowiednią kwotę.

$profit = new Money(5, new Euro);
list($my_cut, $investors_cut) = $profit->allocate(70, 30);

Z pomocą klasy możemy również podzielić pieniądze np. w proporcjach 70/30, co ostatni przykład obrazuje, tworzy kwotę o wartości 5 eurocentów i dzieli ją w podanej proporcji. Co do samego algorytmu dzielenia, jest to ciekawe podejście i zainteresowani na stackoverflow mogą zapoznać się z jego matematyczną analizą.

Konkluzja

Więcej przykładów jest w podanych wcześniej UnitTestach oraz dokumentacji. Idea tego rozwiązania jest prosta, łatwo dodać automatyczne przeliczanie waluty czy też na podobnej zasadzie zrobić podatki, dzięki czemu mamy większą pewność, że system jaki robimy (sklep?) działa dokładnie tak jak powinien, nie będą nam straszne waluty, zaokrąglenia, przeliczenia, procenty, podatki i inne pułapki. I teraz już nikt nie będzie nas pytał, gdzie są pieniądze za las…

Dodaj na LinkedIn
thejw23
Freelancer, programista php, wiecznie niezadowolony ze swojego kodu. Toleruje frontend, kocha backend, miłośnik integracji :)