Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
Moduly / Sklad

Sklad#

Modul Store eviduje množství — sám nic neprodává. E-shop mu říká „rezervuj“, „potvrď“, „uvolni“; sklad na to odpovídá a vede o tom doklady. Kód: app/UI/Api/Store/.

Z čeho se modul skládá#

Entita Role
StockItem skladová karta — vazba na prodejní sadu e-shopu
StockLevel množství na jednom skladu: quantity a reserved
StockBatch šarže — číslo, expirace, nákupní cena
StockDocument doklad (příjemka, výdejka, převodka, inventura)
StockMovement + StockMovementBatch pohyby a jejich rozpad na šarže
StockReservation rezervace
Warehouse, Supplier, Vacation číselníky a dovolené skladů
Služba K čemu
StockReader čtecí model dostupnosti a termínů dodání, bez zámků
StockRecalculator noční přepočet a self-heal
ReservationExpirer uvolňování prošlých rezervací
MarginReporter marže z nákupních a prodejních cen

Tři množství, prodává se podle dostupného#

quantity              fyzicky na skladě
reserved              drženo pro objednávky
dostupné = quantity − reserved

reserved je DENORMALIZOVANÉ — drží se uložené, nedopočítává se

Selhání uprostřed operace může nechat rezervaci bez objednávky. Fyzický stav pak sedí, dostupný ne — zboží je na skladě, ale nejde ho prodat. Nic to nehlásí; najde se to jako „proč to píše vyprodáno“. Proto ten noční přepočet.

Čtení dostupnosti je konstantní počet dotazů

StockReader agreguje přes sklady, karty, dodavatele i dovolené nezávisle na počtu položek, takže výpis celé kategorie e-shopu se dá zjistit levně. Kdo si dostupnost počítá po položkách ve smyčce, vyrobí N+1.

Rozhraní pro e-shop: čtyři operace#

Sklad nabízí navenek čtyři metody a všechny berou SourceRef — dvojici „který modul + jaký typ + které id“ (například objednávka č. 412 z e-shopu):

Metoda Co udělá
reserve($source, $lines, $allowPartial, $expiresAt) drží množství pro daný zdroj
confirm($source, $salePrices, $userId) promění rezervaci ve výdejku
release($source) rezervaci uvolní
post($documentId) potvrdí doklad a teprve tím pohne množstvím

SourceRef slouží zároveň jako klíč idempotence i jméno zámku (cms_store_src_<modul>_<typ>_<id>).

Opakované volání se stejným SourceRef je no-op, ne druhá rezervace

Právě proto se dá přechod stavu objednávky bezpečně zopakovat po pádu. Kdo si vymyslí vlastní klíč, tuhle vlastnost ztratí.

Pořadí zámků je vždy eshop → store

E-shop drží per-order zámek, sklad k němu přidá svůj nad zdrojem. Opačné pořadí by při souběhu vyrobilo uváznutí.

Postup: napojuji nový modul na sklad#

  1. Zaveď SourceRef pro svůj typ dokladu — modul, typ, id. Musí být stabilní napříč opakováními téže operace.
  2. Rezervuj při vzniku závazku (reserve()), s platností odpovídající tomu, jak dlouho závazek žije.
  3. Rozhodni, jestli povolíš částečnou rezervaci. E-shopový checkout ji nepovoluje (allowPartial = false) — zákazník nesmí zaplatit nekryté zboží.
  4. Při dokončení volej confirm(), při zrušení release(). Nikdy nesahej na quantity přímo.
  5. Prokliknij i cestu selhání: co se stane, když operace spadne mezi rezervací a potvrzením.

Přímý UPDATE množství obejde doklady i pohyby

Sklad pak sedí, ale nedá se dohledat proč. Každá změna množství musí projít dokladem — jinak inventura nemá s čím porovnávat.

Doklady#

Příjemka, výdejka, převodka, inventura. Dokud není doklad potvrzený (post()), s množstvím nedělá nic — dá se rozpracovat, opravit i zahodit.

Potvrzený doklad se neopravuje ani nemaže

Vystaví se storno a pak nový doklad. Smazání by rozešlo aktuální stav s historií pohybů — a rozdíl se objeví až při inventuře, bez stopy, odkud vznikl.

Pohyby jsou snímky#

Pohyb ukládá název položky a zůstatek v okamžiku pohybu.

Přejmenování karty nezmění staré pohyby

Je to záměr — doklad má ukazovat, co na něm stálo tehdy. Stejná logika jako u faktury.

Šarže#

Odepisuje se od nejstarší. U zboží s expirací to zároveň odpovídá trvanlivosti.

Prošlá šarže se sama nevyřadí

Zůstane v evidenci a normálně se odepíše. Cron /cron/store/batch/expiry-alert jen upozorní; vyřazení je ruční výdejka.

Rezervace a souběh#

Rezervace vzniká se závazkem a má platnost; po ní se uvolní cronem.

Platnost rezervace musí být sladěná s expirací objednávky

Uvolní-li se rezervace dřív, než vyprší objednávka, zboží se může prodat podruhé — a druhý zákazník zaplatí něco, co už není. Opačný nepoměr je jen neefektivita: zboží zbytečně dlouho blokované.

Cron uvolňování běží à 5 minut

/cron/store/reservation/expire. Delší interval znamená, že zboží zůstává blokované po vypršení — a projeví se to jako „vyprodáno“, ne jako chyba.

Napojení na e-shop#

Skladové operace se vážou na prodejní sadu (ProductSet), tedy na tutéž entitu, kterou e-shop prodává.

Hledání skladové operace na produktu nebo variantě nic nenajde

Vazba je až na sadě. Podrobně E-shop, oddíl o datovém modelu.

Naplánované úlohy#

Endpoint Co dělá Doporučený interval
/cron/store/reservation/expire uvolní prošlé rezervace à 5 minut
/cron/store/batch/expiry-alert upozorní na blížící se expiraci šarží denně ráno
/cron/store/stock/recount noční přepočet a self-heal denormalizovaných hodnot denně v noci

Bez nočního přepočtu se rozdíl v reserved nikdy nesrovná

Denormalizovaná hodnota se opraví jedině tímhle během. Nespuštěná úloha se neprojeví chybou — jen postupně přibývá zboží, které je na skladě a přesto se tváří jako nedostupné.

Kam sáhnout#

Chci Kde
rozhraní rezervací app/UI/Api/Store/Models/Managers/StockReservationManager.php
potvrzování dokladů app/UI/Api/Store/Models/Managers/StockDocumentManager.php
čtení dostupnosti a termínů app/UI/Api/Store/Models/Services/StockReader.php
přepočet a self-heal app/UI/Api/Store/Models/Services/StockRecalculator.php
klíč idempotence a zámku app/UI/Api/Store/Models/DTO/SourceRef.php
cron úlohy app/UI/Cron/Store/Presenters/

Navazující kapitoly: E-shop · Fakturace · Naplánované úlohy · Životní cyklus manageru