Praktický návod

Jak správně ukládat peníze a ceny do databáze

Částku ukládej přesně, měnu vždy explicitně a zaokrouhlení prováděj na předem určené hranici.

20 minut · Databáze

Nejdřív stručně

Peníze nejsou běžný float

Binární float neumí přesně reprezentovat mnoho desetinných částek. Drobné chyby se projeví při součtech, daních a porovnávání, proto se pro peníze nehodí.

Bezpečné jsou dvě běžné reprezentace: přesný decimal jako PostgreSQL NUMERIC, nebo celé číslo v nejmenší používané jednotce. V obou případech musí být součástí hodnoty také měna a pravidlo zaokrouhlení.

Připrav si

Co musíš rozhodnout předem

Datový typ je až důsledek obchodních pravidel. Nejdřív definuj přesnost, měny a okamžik zaokrouhlení.

  • Seznam podporovaných měn a počet desetinných míst používaný pro účtování, ceny a mezivýpočty.
  • Pravidlo zaokrouhlení pro jednotkovou cenu, daň, řádek dokladu a celkovou částku.
  • Rozhodnutí, zda se historický doklad ukládá jako neměnný snapshot místo přepočtu z aktuálního katalogu.
  • Rozsah nejvyšší částky a výpočtů, podle kterého zvolíš precision a scale nebo rozsah BIGINT.

Kroky 1 až 3

Zaveď přesný peněžní model

Vyber jednu reprezentaci pro daný kontext a nemíchej v něm desetinné řetězce, floaty a haléře bez převodní hranice.

1. Vyber NUMERIC nebo nejmenší jednotky

  1. NUMERIC(p, s) ukládá desetinnou hodnotu přesně. Precision a scale zvol podle největší částky a požadovaných mezivýpočtů, ne podle náhodného příkladu.
  2. BIGINT v nejmenších jednotkách zjednoduší sčítání a porovnávání. Ne všechny měny ale používají dvě desetinná místa, proto převod nehardcoduj jako krát sto.
  3. Typ PostgreSQL money nepoužívej jako univerzální doménový model. Jeho vstup a výstup závisí na locale a samotný typ nenese kód měny.
  4. Pro katalogovou cenu s výpočty DPH bývá NUMERIC praktické; pro uzavřené platební částky může být vhodný integer minor amount. Rozhodnutí zdokumentuj.
amount NUMERIC(19, 4) NOT NULL, currency CHAR(3) NOT NULL
Oficiální PostgreSQL dokumentace k číselným typům

2. Přenášej částky bez floatu

  1. Z API a formuláře přijmi desetinnou částku jako validovaný řetězec. V PHP ji neposílej přes float před uložením do NUMERIC.
  2. Pro výpočty použij decimal nebo money knihovnu s explicitním rounding mode. Řetězec 19.90 musí zůstat přesnou hodnotou 19.90.
  3. Částku a ISO kód měny drž pohromadě ve value objectu. Nedovol sčítat EUR a CZK bez explicitního směnného kurzu.
  4. Do databáze neposílej formátované hodnoty s čárkou, měnovým symbolem nebo oddělovačem tisíců. Formátování patří až do výstupu.
new MoneyAmount('19.90', 'CZK')
Oficiální PHP dokumentace k přesnosti floatu

3. Urči jedinou hranici zaokrouhlení

  1. Pojmenuj rounding mode a místo, kde se použije. Zaokrouhlovat po každé mezihodnotě a potom znovu na konci dává jiné výsledky.
  2. U faktury ulož jednotkovou cenu, množství, sazbu, základ, daň, celkovou částku a měnu tak, jak byly při vystavení schválené.
  3. Databázové constrainty použij pro formát měny a platný rozsah. Záporná částka může být správná pro dobropis, proto zákaz odvoď z významu sloupce.
  4. Stejné příklady a rounding mode sdílej mezi backendem, účetním exportem a testy. Frontendový náhled nesmí být jediným výpočtem.
rounding: half-up; scale: 2; apply: invoice line tax
Oficiální PHP dokumentace k zaokrouhlení

Krok 4

Otestuj přesnost i hranice

Testuj hodnoty, na kterých se reprezentace a zaokrouhlení opravdu lámou, ne jen celé koruny.

  1. Sečti problematické desetiny

    Ověř přesný výsledek 0.10 + 0.20 a ukládání nul na konci podle zvoleného scale.

    SELECT 0.10::numeric + 0.20::numeric;
  2. Otestuj poloviny a záporné částky

    Přidej příklady přesně na hranici zaokrouhlení, dobropis, nulu a maximální povolenou hodnotu.

    php bin/phpunit --filter Money
  3. Porovnej celý doklad

    Součet řádků, daní a celku musí odpovídat schválenému účetnímu příkladu a zůstat stejný po načtení z databáze.

Když to zlobí

Nejčastější chyby

19.90 se po cestě změnilo na nepřesný float

Najdi převod na float v requestu, DTO nebo serializaci. Decimal přenášej jako řetězec nebo money value object až k databázovému driveru.

Součet řádků nesedí s celkem faktury

Tým zaokrouhluje na jiné hranici nebo jiným režimem. Definuj, zda se zaokrouhluje řádek, daň nebo až celek, a ulož výsledný snapshot.

Částka v integeru je pro některou měnu stokrát vedle

Převod předpokládá dvě desetinná místa. Použij metadata konkrétní měny a měj explicitní hranici mezi major a minor units.

Databázový typ money se formátuje jinak na serveru

Je závislý na locale. Pro přenositelný model použij NUMERIC nebo BIGINT a samostatný kód měny.

Hotovo

Částky jsou přesné a jejich význam úplný.

Databáze teď ukládá přesnou hodnotu, měnu a obchodně domluvené zaokrouhlení. Stejný model používej od vstupu přes výpočet až po neměnný doklad.

Zavolejte mi

Zavolám vám následující pracovní den mezi 9:00 a 17:00.

Můžete mi také zavolat rovnou.

+420 605 181 728

Nechte mi telefonní číslo a pošlete žádost o zpětné zavolání.

Odesláním souhlasíte se zpracováním údajů pro vyřízení žádosti.