| Jak na ceny v Eshopu Návod, jak programovat v PHP ceny v rámci eshopu | 13. srpen 2010 Poslední aktualizace 12. 02. 2011 16:15:52 |
Rozhodl jsem se, že na tohle téma napíšu vlastní článek. Není to totiž nic jednoduchého určit, jakým způsobem se vlastně v kódu počítají
ceny, jaká cena se zobrazuje a jaká se ve skutečnosti vypočítává.
V tomto článku se dočtete, jak správně postupovat a programovat při vypočítávání ceny, co je třeba vědět a čeho je třeba se vyvarovat.
Vše v populárním open-source programovacím jazyce PHP, příručka ale může v lecčems poradit i programátorům v jiných
programovacích jazycích na jiných serverech (ASP.NET, Java ...).
Pokud hodláte prodávat eshop, který neprovádí žádné výpočetní operace s cenou tak ani nemusíte pokračovat ve čtení tohoto článku, máte prostě
uložené jedno číslo v databázi a to zobrazujete. Jednoduché.
Ale kdo by od vás takový eshop koupil, že ;) Maloobchodníci prodávájí za cenu s DPH a vy musíte pro ně to DPH dopočítat, nebo naopak, z ceny s DPH vypočítat cenu bez DPH. Myslíte si, že to je jasná záležitost? Čtěte dál.
Pokud jste četli můj předchozí článek o PHP pro začátečníky, zmiňuji se zde o typu (float) což
je v php typ reprezentující desetinná čísla.
Nutno dodat, že v PHP je toto jediný typ pro desetinná čísla.
Podle manuálu
existují i typy (double) a (real), ve skutečnosti jde o aliasy typu (float). Velikost floatu je
variabilní v závislosti na procesoru, na kterém se PHP nachází.
Počítače vše počítají v binární soustavě - nuly a jedničky. Číslo 10 je v desítkové soustavě číslo 2, 110110 je číslo 54. To je všechno ok,
ale i binární soustava počítá s desetinnými čísly.
Kolik je v desítkové soustavě 10,1? Výsledek: 2,5.
10,011 = 2,375.
Ale co naopak? Kolik je v binární soustavě číslo 2,6 ?
Výsledek: 10.10011001100110011001100110011001....a do nekonečna opakující se
1001 v desetinné části. Počítač ale nemůže iracionální nebo periodická čísla reprezentovat nekonečně přesně, procesor nemá nekonečně širokou sběrnici, takže
v nějaké části číslo ořízne a 2,6 nebude 2,6 ale stane se z něj 2,5999999046325684.
Počítač navíc neukládá desetinné číslo přesně v tom formátu, v jakém jsem ho napsal ale ve formátu
IEEE 754, který mu umožňuje jednoduššeji
provádět výpočetní operace.
V počítači tedy není možné reprezentovat desetinné číslo s maximální přesností, neboť i pro binární soustavu
mohou po převodu vznikat čísla s neukončeným periodickým rozvojem v desetinné části a musí se někde odstřihnout,
čímž dojde ke ztrátě přesnosti.
Teď vás nejspíš napadá argument: vždyť já nepotřebuji dokonalou přesnost, stačí mi čísla zaokrouhlovat na dvě desetinná místa,
halíře se nepočítají na tisíciny. A navíc, dneska má skoro každý 64 bitový procesor což dává prostor pro ještě přesnější čísla.
No jo, jenže když zaokrouhlíte čísla, jejichž skutečná reprezentace není přesně to číslo, které vidíte na obrazovce, vyjde vám to blbě.
Mějme následující učebnicový příklad:
<?php
$p = (0.7+0.1) * 10;
echo floor($p);
?>
Co z výše uvedeného vyplývá?
Na (float) se nemá cenu spoléhat. Jde o peníze a potřebujeme se na veškeré výpočetní operace spolehnout
a nesmíme se minout ani o haléř.
V eshopu se nemusí výpočet ceny týkat jen daně, můžete naimplementovat třeba modul který jednotlivé produkty navýší o
procentní marži nebo naopak sníží o nějaké slevy, akce, kupony, smluvní dohody o cenách od jednotlivých dodavatelů a bůhví,
co všechno si takový čiperný podnikatel může vymyslet.
Verdikt? Pro účely počítání cen NIKDY NEPOUŽÍVEJTE FLOAT.
V PHP je knihovna BCMath, jejíž používání neobsahuje rizika spojená s používáním typu float.
Tato desetinná čísla se totiž používají jako řetězce a funkce této knihovny jsou matematické operace.
BCMath se stará o přesnost na počet desetinných míst,
které si sami zvolíte, třeba na 100, to je jedno, přesnost máte vždy zaručenou protože BCMath nepracuje s floaty ale
řetězci, které sám propočítává celočíselnou aritmetikou a s floatem nemá nic společného.
Jednotlivé funkce BCMath ani nepotřebují abych je nějak rozebíral, jsou to funkce, kde první operátor je levá strana rovnice a druhý operátor je
pravá strana rovnice. Jednotlivé funkce reprezentují pak matematickou operaci a výsledkem je buď string (výsledek matematické operace) nebo
boolean (výsledek porovnání), viz. zde.
<?php
$c = bcadd ($a, $b); //$c = $a + $b;
$c = bcsub ($a, $b); //$c = $a - $b;
$c = bcmul ($a, $b); //$c = $a * $b;
$c = bcdiv ($a, $b); //$c = $a / $b;
$c = bcpow ($a, $b); //$c = $a ^ $b;
$c = bcsqrt($a); //$c = sqrt($a);
?>
Ještě než začneme kapitolu o tom, jak se ceny počítají tak je nutné si nejdřív říct, jaké ceny chceme zobrazovat.
První věc, kterou je nutné brát v potaz: BCMath nezaokrouhluje. Pokud u BCMath nastavíte přesnost na dvě desetinná místa, z čísla 5,749999 se stane 5,74 (další desetinná místa se zahodí).
Na zaokrouhlování nemůžeme využít funkce floor, ceil nebo round, neboť tyto funkce pracují
jen s floaty, nikoliv s řetězci.
Budeme muset napsat vlastní funkce ale to nebude tak těžké, jak si myslíte.
A pokud nejsme úplně blbí, vygooglime hotový kód ze StackOverflow :D
<?php
//bcceil - zaokrouhlení na celá čísla nahoru
function bcceil($number) {
if ($number[0] != '-') {
return bcadd($number, 1, 0);
}
return bcsub($number, 0, 0);
}
//bcfloor - zaokrouhlení na celá čísla dolů
function bcfloor($number) {
if ($number[0] != '-') {
return bcadd($number, 0, 0);
}
return bcsub($number, 1, 0);
}
//bcround - matematické zaokrouhlení na $precision desetinných míst
function bcround($number, $precision = 0) {
if ($number[0] != '-') {
return bcadd($number, '0.'.str_repeat('0', $precision).'5', $precision);
}
return bcsub($number, '0.'.str_repeat('0', $precision).'5', $precision);
}
?>
Jak lze vlastně cenu zaokrouhlit? Není zaokrouhlování nějak určeno zákonem?
Je a není - zákon se zmiňuje o zaokrouhlování jen při výpočtu daně ale co se týče finálního zaokrouhlení ceny,
obchodník jí může zaokrouhlit jak chce - na haléře, desetihaléře a padesátihaléře nebo celé koruny.
Z toho vyplívá, že obchodník si sám může zvolit, jakou zaokrouhlovací metodu zvolí.
Může chtít zaokrouhlovat vše dolů, vše nahoru, matematickým zaokrouhlením, postupným zaokrouhlením...
(jedná se však o stanovení ceny, při vyplňování daňových formulářů jsou zákony ohledně zaokrouhlování o něco striktnější)
Takže si domyslíme, že v kódu potřebujeme počítat nejméně na 3 desetinná místa, neboť pro zaokrouhlování na nejmenší
povolenou jednotku - na haléře - je třeba třetí desetinné místo.
Příklad: Máme vypočtenou cenu 15.544, obchodník má tři volby:
15,54 - při zaokrouhlování dolů
15,55 - při zaokrouhlování nahoru
15,54 - při matematickém zaokrouhlování na dvě desetinná místa
Mnojo, jenže s tím, že obchodník může zaokrouhlovat jak chce je trochu problém.
Co když vám řekne, že veškeré výsledky chce třeba postupným zaokrouhlováním z 8 desetinných míst?
No, odpověď je jednoduchá. Budete mu muset takovou funkci napsat :D Na druhou stranu, nemělo by to být tak těžké, bcscale nastavíte na 8 a tím pádem
se všechny ceny budou počítat na 8 desetinných míst a pak napíšete funkci, kde odtrhnete desetinou část z řetězce a budete ho v cyklu procházet
od konce do druhého desetinného místa, každé desetinné místo zaokrouhlíte a podle výsledku do další
iterace přenesete prázdnou hodnotu nebo navýšení.
Navíc, jakmile jednou tuto funkci napíšete, budete jí moci využít pro každého dalšího šíleného obchodníka.
Řekněme, že máme obchodníka č.1, hodně tvrdohlavého obchodníka, který chce eshop bez desetinných míst úplně.
Obchodník č. 1:
Nechci vůbec na eshopu desetinná místa, nastavíme bcscale(0) a veškeré výpočty se budou zaokrouhlovat
prostým matematickým pravidlem.
Obchodníkův argument:
zákon 634/1992 Sb. §3 odst. c)
nám říká: při konečném účtování prodávaných výrobků a poskytovaných služeb v hotovosti se celková částka
zaokrouhluje vždy k nejbližší platné nominální hodnotě zákonných peněz v oběhu.
Protiargument:
To neznamená, že každý obchodník za každou cenu musí všechny své ceny zaokrouhlit!
Zákon nám jen říká, jak zaokrouhlit platbu v hotovosti ale v bezhotovostním styku
desetinná místa stále fungují. Je v režii banky,
jak se s desetinnými částkami vypořádá. Pokud chcete v eshopu mít Paypal nebo platební systém České Spořitelny,
je nutné s tím počítat!
Definitivní protiargument:
Dále je třeba počítat s €ury, které se časem v ČR objeví. A v eurech se platí centy úplně běžně, bylo
by zatraceně krátkozraké předpokládat, že moje ceny budou vždy celým číslem. Proč? Při zavedení eura se dá čekat, že vláda vydá zákon, kterým
všem obchodníkům přikáže do jaké míry smějí ceny zaokrouhlovat, jinak by se zde vše pochopitelně začalo zdražovat což by do nějaké míry hnulo
s ekonomikou celého republiky.
Někteří obchodníci mají velmi striktní cenovou politiku, do které nechtějí zasahovat (příkladem Baťa, a jeho ceny nikdy nekončící
na nic jiného než na devítky). Od takových obchodníků/manažerů je třeba zjistit,
jestli s přechodem na Euro počítají. Pokud nebudete počítat s desetinnými místy a ČR přejde na Eura, budete muset eshop překopat a
pokud nebudete mít část pro výpočet zobrazované ceny v kódu centralizovanou třeba v jedné třídě,
vyhodí vás z práce, protože to nestihnete a klient nezaplatí.
Představme si obchodníka č. 2.
Obchodník č.2: Veškerý můj sortiment má stanovené ceny s DPH, nikdy nemám cenu
bez DPH. Chci mít možnost vkládat do administrace ceny S DPH a na frontendu chci zobrazovat obě ceny: s DPH i bez DPH. Chci, aby klient
při potvrzování objednávky viděl, kolik činí samotné DPH u jednotlivých produktů. Částku zaokrouhluji na haléře nahoru.
S Eury počítám, po přechodu na Euro naimportuju jiný ceník.
Rozbor: Potřebujete z konečných cen s DPH vypočítat částku bez DPH a DPH samotné.
Možná si říkáte: vypočítat DPH je logické, pro produkt s cenou 299 a daní 20% vypočtu přímo cenu bez DPH 299 / 1,20 a
výsledek je 249,166666...
Omyl!
V rozboru jsme udělali hned dvě chyby.
Za prvé - mějte na vědomí, že při dělení nám může vyjít číslo s neukončeným desetinným periodickým rozvojem...prostě s
donekonečna se opakujícími čísly, my však počítáme jen na 3 desetinná místa.
Musí nám vycházet rovnost a pokud u jiného obchodníka zvolíme zaokrouhlování dolů, nebude nám tímto postupem nic vycházet.
299 / 1,20 = 249,16 ale naopak to nevyjde - 249,16 * 1,20 = 298,99! Tento postup je špatný už jen kvůli tomu dělení,
v obecné rovině počítání cen se chceme zbavit všech "desetinně neukončených" čísel.
Za druhé - pokud se zjistí, že obchodník byl vrah, lupič, podvodník co okradl stát na daních o miliony a uteče na Bahamy,
policie si prohlídne vše, co tu nechal, mimo jiné i jeho
eshop. Z jeho zdrojáku zjistí, že jste špatně počítali daň, protože CENA BEZ DPH * 1,20 nebo CENA S DPH / 1,20
není zákonem stanovený postup pro výpočet DPH.
Rozumím vám, říkáte si, WTF? Vždyť jak jinak spočítat daň než normální matematickou operací?
DPH se musí počítat zákonem stanoveným způsobem. Jsou dva odstavce, jeden říká, jak počítat daň pro cenu s dph a druhý který nám říká, jak z ceny, kde je DPH už zahrnuto spočítat právě tuto daň respektive cenu bez DPH.
Přesné znění je v zákoně o dani z přidané hodnoty č. 235/2004 Sb. §37, odstavec 1) a 2)
(1) Daň se vypočte ze základu daně stanoveného podle § 36 jako součin úplaty za zdanitelné plnění bez daně a koeficientu, který
se vypočítá jako podíl, v jehož čitateli je číslo 20 v případě základní sazby daně nebo číslo 10 v případě snížené sazby
daně a ve jmenovateli číslo 100, vypočtený koeficient se zaokrouhlí na čtyři desetinná čísla, vypočtená daň se uvede
způsobem podle § 28 odst. 2 písm. l). Cena včetně daně se pro účely tohoto zákona dopočte jako součet základu daně
a vypočtené daně po případném zaokrouhlení.
(2) Daň může plátce rovněž vypočítat z úplaty za zdanitelné plnění, která je včetně daně, nebo z částky stanovené podle
§ 36 odst. 6, která je včetně daně, a koeficientu, který se vypočítá jako podíl, v jehož čitateli je číslo 20 v případě základní
sazby daně nebo číslo 10 v případě snížené sazby daně a ve jmenovateli součet údaje v čitateli a čísla 100, vypočtený koeficient
se zaokrouhlí na čtyři desetinná místa, vypočtená daň se uvede způsobem podle § 28 odst. 2 písm. l).
Cena bez daně se pro účely tohoto zákona dopočte jako rozdíl částky za zdanitelné plnění obsahující daň a vypočtené daně po
případném zaokrouhlení.
A já si myslim, že jste ty odstavce se zákony vůbec nečetli protože se to čte blbě a je to nuda. Chápu vás.
Obchodník č.2 používá jen ceny s dph ale i přesto náš eshop musí umět vypočítávat cenu BEZ dph do kalkulací v administraci. Zákon
nám říká, že se DPH vypočítá násobením částky koeficientem-zlomkem,
v jehož čitateli je výše daně a ve jmenovateli 100 + výše daně, výsledek je zaokrouhlený na
4 desetinná místa.
V matematice platí, že 20 / 120 * cena = cena - (cena / 1,2) jenže zákon explicitně
říká, že výsledek (20 / 120) je třeba ještě zaokrouhlit na 4 desetinná místa
Pokud jste matematicky myslící tvor, pak jste v tuhle chvíli celý princip pochopili.
Pokud nejste matematicky myslící tvor (jako já), pak mám pro vás následující tabulku,
která vám to osvětlí.
|
Výsledky jsou podle požadavků obchodníka vždy zaokrouhleny na 2 desetinná místa nahoru. |
|||
| Cena S DPH | Matematický výpočet: daň = cena - (cena / 1,2) |
Zákonem stanovený výpočet:
daň = cena * 0,1667 |
O kolik je částka stanovená zákonem vyšší než matematicky vypočtená částka - neboli o kolik bychom mohli eventuelně stát okrást |
| 5,- | 0.83 | 0.83 | 0.00 |
| 10,- | 1.67 | 1.67 | 0.00 |
| 19,- | 3.17 | 3.17 | 0.00 |
| 48,- | 8.00 | 8.00 | 0.00 |
| 299,- | 49.83 | 49.84 | 0.01 |
| 1999,- | 333.17 | 333.23 | 0.06 |
| 5999,- | 999.83 | 1000.03 | 0.20 |
| 10000,- | 1666.67 | 1667.00 | 0.33 |
| 59999,- | 9999.83 | 10001.83 | 2.00 |
| 100000,- | 16666.67 | 16670.00 | 3.33 |
| 599999,- | 99999.83 | 100019.83 | 20.00 |
| 1500000,- | 250000.00 | 250050.00 | 50.00 |
| 29999990,- | 4999998.33 | 5000998.33 | 1000.00 |
Teď je to jasné všem - zákonem stanovená metoda totiž saje do státní pokladny o něco víc peněz,
než matematická metoda. Možná si ťukáte na čelo a říkáte si, že v eshopu vaší firmy se přeci
nikdy nebudou prodávat částky za desetitisíce, statisíce a miliony a koho zajímají nějaké haléře...
Obchodníka to zajímá. Zákazníky to zajímá! Všechny to zajímá! Je zatraceně hloupé si myslet, že
je přeci jedno když nedodržujeme zákony byť v nich jde jen o haléře. Ano,
máte pravdu pokud mi řeknete, že to nejspíš ani většina maloobchodníků vašeho malého eshopu neví.
A chcete vědět, jak zareagují, až se to dozví? Líbit se jim to nebude.
Panikaříte, protože jste tohle nevěděli a už těch eshopu jste prodali moc?
Nemusíte, věřte mi, že eshopy, kde se cena počítá přes float za pomoci primitivní matematiky bez znalosti zákonů
prodává nejspíš většina firem.
Prošel jsem přes pár zaměstnavatelů, žádný z nich o zákonem stanoveném způsobu výpočtu DPH nevěděl a žádný z klientů
(kterých bylo pochopitelně mnohem víc) se na to ani nikdy neptal.
První odstavec říká,
že se DPH z ceny BEZ DPH vypočte vynásobením ceny číslem (daň / 100) zaokrouhleným na 4 desetinná místa
a celková cena je pak cena bez dph + daň...
tj. 20 / 100 = 0,2000 ...tedy 0,2 a to je LOL, že jo, protože tenhle postup dá jiné číslo,
než postup opačný.
daň z 1.000.000 bez daně = 200.000 Kč
ale
daň z 1.200.000 s daní = 200.040 Kč
Tohle nedává smysl, že?
Vtip je totiž v tom systému, jakým DPH funguje. V řetězci mezi spotřebitelem a dodavateli nemáte vždycky k
dispozici cenu bez DPH, navíc, někdo je plátcem a někdo není plátcem. To už bych ale příliš odbočoval k daním.
Jak ukládat cenu?
Vycházím z toho, že programujete úplně obyčejný eshop a nenapadá vás žádný ideální způsob.
Pokud součástí vašeho eshopu je dynamická aktualizace cen
podle nějakých analýz, pak nejspíš sami dobře víte, jak s takovýma cenama pracovat.
Cenu jako takovou ukládejte do databáze (pochopitelně k tabulce která se týká dané položky)
jako desetinné nezáporné číslo minimálně na 3 desetinná místa. Vždy vycházejte z toho, že jde o cenu BEZ DPH,
které eshop vypočte, pokud obchodník do administrace zadá cenu včetně DPH. Takto s cenou zacházejte i pokud
klient používající váš eshop vlastně ani není plátce a DPH na svém eshopu nechce. Jednou se z něj plátce
třeba stane a pak je mnohem jednodušší někde jen přepnout nějaký boolean než překopávat celý eshop.
Číslo potřebujete zaokrouhlené nejméně na 3 desetinná místa, neboť v bezhotovnostním styku (PayPal,
platební brána ČS, bankovní příkaz) je možné částku zaplatit i s haléři...a aby jste měli možnost zaokrouhlit
na haléře, musíte mít k dispozici číslo ještě za tím haléřem.
Jak zobrazovat a zaokrouhlovat cenu?
Tak jak chtějí obchodníci - a každý obchodník to chce jinak a nikdy to nechce všude stejně. Takže třeba v košíku chce
zobrazovat vše s desetinnými čísly a výsledek zaokrouhlit na celé koruny, některý chce zase v košíku vše zaokrouhlovat
na celé koruny a zobrazovat na zbytku eshopu ceny na jedno desetinné místo, někdo jiný zase chce zobrazovat částky na dvě
desetinná místa jen cenu bez DPH nebo s DPH nebo se slevou...
A co ceny na backendu? Jaké ceny zobrazit v objednávkách, jaké na fakturách? Možností je hrozně moc!
Jak ukládat DPH?
Stejným způsobem, jakým je DPH reprezentována ve skutečnosti - avšak ukládejte jí jako desetinné číslo.
Že je teď základní sazba 20% neznamená, že jí vláda nesníží jen o 0,03 procenta, jakkoliv úchylně by sazba 19,97% vypadala.
Jak to celé nějak naprogramovat?
Motigovo pravidlo pro začátečníky:
Na frontendu na hlavní stránce zobrazuji 5 řádků po 4 sloupcích, celkem 20 položek. První řádek obsahuje
jen zboží v akci seřazeno podle data přidání, další zboží je vybrané zcela náhodně.
Špatné řešení databáze: Akční cenu mám uloženou jako sloupec k tabulce s položkami.
Pokud je obsah sloupce NULL nebo 0, zboží v akci není, jinak je akční cena právě ta, která je v tomto sloupci.
Správná řešení databáze: Akční ceny mám uložené ve vlastní tabulce nebo v jiném
relačním schématu.
Špatná implementace: Provedu dva dotazy: jeden dotaz, kde JOINuji sloupec s akčními cenami,
druhý dotaz, kde vypisuji normální ceny. Pak data procházím normálně v cyklech, v jednom vypíšu sloupec s akční cenou, v druhém cyklu
vypíšu sloupec s normální cenou.
Správná implementace: Objektová implementace - jednotlivé zboží je reprezentováno objekty jejichž
ceny jsou též reprezentovány objekty. Mám stále dva dotazy nebo jiný, nacachovaný zdroj dat, ve kterém mám vše z databáze předem vyndané.
Pokud chci zásadně dodržovat OOP abstrakci tak mám filtrační metody, kterými vyberu jednou nejdřív akční zboží a pak náhodně seřazené ostatní zboží,
nebo na menší úrovni abstrakce zavolám nějaké dva dotazy, které oba berou stejné sloupce a stejné joiny, jenom mají jiný "WHERE", výsledky dotazů
jsou pak zdrojem dat pro konstrukci objektů. Při výpisu ceny
si pak z objektu ceny mohu vybrat, jakou cenu chci zobrazit.
Cena se skládá z vlastní marže, dodavatelské marže a pokud je zákazník na eshopu přihlášený
tak je cena ještě upravena o částku dočasného slevového kuponu, kterou zákazník může vložit prostřednictvím
nastavení svého účtu na frontendu.
VELMI špatná implementace:
Všude kde se zobrazuje cena beru ještě data z tabulek vlastních marží, dodavatelských marží a z tabulky kupónů klientů.
Před zobrazením ceny částku vždy spočítám a pak zobrazím.
Pořád špatná implementace:
Všude kde se zobrazuje cena beru ještě data z tabulek vlastních marží, dodavatelských marží a z tabulky kupónů klientů.
Počítání už nedělám, protože na to jsem si napsal nějakou funkci, do které dám všechny parametry, které potřebuje a vrátí mi
správně spočítanou cenu.
Lepší, ale pořád špatná implementace:
Všude kde se zobrazuje cena beru ještě data z tabulek vlastních marží, dodavatelských marží a z tabulky kupónů klientů.
Počítání už nedělám, protože na to jsem si napsal nějakou funkci, do které dám ARRAY parametrů, tudíž mohu funkci
v budoucnu rozšířit o jakékoliv další možné propočty s funkcí aniž bych furt jak blb psal funkci s miliardou parametrů
na několik řádků.
Správná implementace: OOP: jednotlivé zboží je reprezentováno objekty jejichž
ceny jsou též reprezentovány objekty. V závislosti na tom jaké zboží chci zobrazit buď zavolám dotaz se sloupci, které
objekt potřebuje pro správnou funkčnost nebo mám jiný, nacachovaný zdroj dat, ve kterém mám vše z databáze předem vyndané.
Při výpisu ceny pak vytáhnu již hotovou vlastnost z objektu ceny ke které se dostanu např. přes
$seznam_zbozi_hlavni_stranka->zbozi[0]->cena->finalni_cena
nebo
$seznam_zbozi_hlavni_stranka->getZbozi(0)->getCena()->getFinalniCena()
Sejde na tom, jak se vám to líbí víc.
Špatná implementace objektu ceny:
Objekt ceny mi sám zjistí všechny potřebné údaje z databáze.
Správná implementace objektu ceny:
Všechny údaje o maržích, dodavatelských maržích a kuponech mám buď nacachované
(např. ve statických vlastnostech objektu ceny)
nebo všechny JEDNOU vybrané z databáze tak, abych nemusel posílat miliardu dotazů na databázi jak magor
pro zjištění ceny každé položky, kterou chci zobrazit.
Uvědomte si také, že při počítání ceny hrozně záleží na pořadí.
Tady už ale záleží na implementaci databáze, správný přístup však je všechny tabulky
ovlivňující cenu cachovat a veškerá data mít kdykoliv k dispozici.
Jak už jsem jednou psal, nestalo se mi, aby obchodník najednou změnil svůj požadavek v tom jak zobrazovat cenu (což pochopitelně neznamená, že se to nemůže stát nikdy). Ono to i dává smysl - pro obchodníky je moc důležité, jakou cenu nabízejí a jak ta cena vypadá v eshopu, v objednávce a nebo na faktuře. Tohle mě spíš vede k tomu mít možnost nastavit zobrazení ceny a zaokrouhlování přes nějakou programátorskou administraci, interní XML nebo INI soubor nebo konfiguraci z databáze dosažitelnou jen z phpmyadminu (záleží na vaší implementaci).
Tabulka toho, co by váš eshop měl umět nastavit:
| Zaokrouhlovat na: |
|
| Zaokrouhlovací metoda: |
|
| Zobrazit zaokrouhlení (běžné na fakturách): |
|
| Kam nastavení aplikovat: |
|
Tady už ode mně máte jen doporučení co se týká samotného kódu.
Protože se cena dá ovlivnit nekonečně mnoha způsoby, definuji termín
"finální cena". Finální cena je ta cena, která se právě zobrazuje. Finální
cena NENÍ cena produktu, kterou klient skutečně zaplatí, protože pokud
váš eshop umožňuje při vyřizování objednávky vložit slevový kupon na konkrétní zboží,
finální cena se upraví.
Já osobně mám rád přístup k vlastnostem přes __get a u ceny zrovna tak.
Vzhledem k různorodosti požadavků obchodníků přistupuji k vlastnostem ceny pomocí
metody __get, která mi zjistí, co vlastně chci a podle toho obvolá
privátní metody třídy.
<?php
$zbozi->cena->finalni_cena
//obsahuje hlavní, finální cenu, kterou klient zaplatí.
//tato cena je zobrazena a zaokrouhlena podle nastavení eshopu
$zbozi->cena->finalni_cena_ceil
//obsahuje hlavní, finální cenu, kterou klient zaplatí,
//zaokrouhlenou na celá čísla pomocí bcceil
$zbozi->cena->finalni_cena_round2
//obsahuje hlavní, finální cenu, kterou klient zaplatí,
//zaokrouhlenou na haléře (číslo za "round" je použito jako druhý parametr
//pro funkci bcround)
$zbozi->cena->finalni_cena_bez_dph
//Obsahuje hlavní, finální cenu, bez DPH.
//Tato vlastnost nemusí existovat, pokud obchodník není plátcem
//a DPH nikde nezobrazuje. Závisí na vaší implementaci, jestli půjdete
//striktní cestou exceptionů, nebo budete vracet prázdnou hodnotu
$zbozi->cena->finalni_cena_bez_dph_ceil
$zbozi->cena->finalni_cena_bez_dph_round2
$zbozi->cena->akcni_cena_rozdil
$zbozi->cena->akcni_cena_rozdil_ceil
$zbozi->cena->akcni_cena_rozdil_round2
//UŠETŘÍTE AŽ X !!!, kde X je hodnota této proměnné...
//může být v procentech či číslech a existuje jen u zboží,
//které je zrovna v akci.
?>
Budu vděčný za jakékoliv připomínky a komentáře! x)