[ BLOG // 2026-07-17 // 4 MIN ]

Expediční deska: jak poslat zboží z domu jedním klikem

Proč nestačí dodací list na každou objednávku zvlášť — a jak jedna stránka seskupená podle rozvozových tras, jeden tlačítko na celou trasu a jeden časový pruh změnily skladový provoz.

PROJEKTY nextjseshopautodilyworkflowcase-study

Když máte desítky objednávek denně, jedno výdejní okénko a tři rozvozové trasy, rozhodování „co teď naložit do auta” není triviální. V B2B e-shopu s autodíly to donedávna vypadalo takhle: admin otevřel objednávku, podíval se, jestli už na ni přišlo zboží, případně vytvořil dodací list, pak otevřel další — a tak pořád dokola, dokud neprojel všechny.

Teď je tu expediční deska. Jedna stránka, která ukáže všechno, co je ready k odeslání, seskupené podle rozvozových tras. Každá objednávka je jeden řádek, každá trasa jeden blok, každý dodací list jedno kliknutí.

Co dřív trvalo minuty

Představte si sklad. Na policích leží zboží, které dorazilo od dodavatelů a prošlo příjmem. K němu existují objednávky, u kterých zákazníci čekají, až jim to dovezeme. Ale nikde nebyl jediný seznam, který by řekl: „Tohle je hotové, může ven.”

Admin musel:

  1. Otevřít seznam objednávek.
  2. Každou rozkliknout — podívat se na stav položek, zkusit odhadnout, jestli je všechno na skladě.
  3. Když jo, ručně vytvořit dodací list.
  4. Když ne, aspoň poznamenat, co chybí.
  5. Po cestě si pamatovat, která objednávka patří na kterou trasu.

Opakovat 40× denně.

Expidiční deska: pohled shora

Deska je postavená na jednom jednoduchém dotazu: všechny rozpracované objednávky, které mají na skladě aspoň jednu položku. Žádný magic, žádný CRON, žádná cache. Každý načtení desky je čerstvý SQL dotaz.

┌─ Trasa: Kolín-jih ──────────────────────────┐
│   KO-2405  Autoservis Novák    4/4 ✅  8 200│  [Vystavit]
│   KO-2403  Pneuservis Král     2/3 ⚠  3 100│  [Vystavit]
│   KO-2401  Autodíly CZ          3/3 ✅  5 400│  [Vystavit]
├─ [Vystavit celou trasu jedním klikem] ──────┤
│                                              │
├─ Trasa: Kutná Hora ──────────────────────────┤
│   KO-2406  Opravna Svoboda     1/1 ✅   700│  [Vystavit]
│ ...                                          │
└──────────────────────────────────────────────┘

Každý řádek ukazuje:

  • Číslo objednávky a zákazníka — odkaz na detail.
  • Připravenost4/4 ✅ = všechny čtyři položky ready, 2/3 ⚠ = dvě ze tří.
  • Hodnotu — co se zrovna veze, v korunách.
  • Tlačítko Vystavit — jedno kliknutí = vytvoření dodacího listu.

Objednávky jsou automaticky seskupené podle doručovací trasy, kterou má zákazník nastavenou. Trasy jsou seřazené abecedně, ta bez trasy (osobní odběry) je dole.

Jeden klik na objednávku

Když admin zmáčkne Vystavit, stane se v jedné transakci tohle:

  1. Ověří se, že objednávka pořád existuje a není zrušená.
  2. Zkontroluje se, že všechny vybrané příjemky jsou k ní přiřazené a mají dostatek zboží.
  3. Vytvoří se rovnou vystavený dodací list (žádný draft — zboží je fyzicky na skladě, admin ho chce poslat).
  4. Pokud tímto odesláním zmizí poslední kus poslední položky, objednávka se automaticky dokončí a zákazník dostane e-mail.
  5. Zaloguje se do auditní stopy.

Všechno v jedné sériově izolované transakci. Kdyby dva admini zmáčkli tlačítko ve stejnou chvíli, jeden z nich uvidí „množství překračuje dostupné zboží” — nikdy nevzniknou dva dodací listy na stejné zboží.

To je důležité. V provozu, kde „běžím na kafe a cestou zmáčknu expedici” je normální, se concurrent click stane dřív, než čekáte.

Celá trasa jedním klikem

Tlačítko Vystavit celou trasu udělá přesně to samé — ale pro každou ready objednávku na trase najednou. Pak otevře PDF se všemi dodacími listy na té trase, připravené k tisku.

To je workflow, který dřív trval 15 minut a vyžadoval otevření pěti záložek. Teď je to jeden click, jedno PDF, jedna cesta k tiskárně.

// Zjednodušeně — reálně je to server action s transakcí
export async function issueRound(routeName: string) {
  const orders = await loadReadyOrdersOnRoute(routeName);
  for (const order of orders) {
    await issueDeliveryNoteForOrder(order.id, order.lots);
    // Každá objednávka je vlastní transakce — jedna chyba nezastaví celej náklad
  }
  // Objednávky, které se dokončily, pošlou zákazníkům e-mail
  notifyCompleted();
}

Časová osa objednávky

Souběžně s expediční deskou vznikl OrderTimeline — vizuální ukazatel životního cyklu objednávky na jejím detailu.

  [1. Nová] ───── [2. Na skladě] ───── [3. Dodací list] ───── [4. Faktura]

Každý stav je buď hotový (vyplněný, primární barva), aktuální (kroužek, na který se čeká), nebo budoucí (šedý, neaktivní). Admin nemusí lovit informace po celé stránce — jeden pohled stačí.

Timeline je čistě deklarativní komponenta: dostane pole {label, done} a sama si spočítá, který krok je aktuální.

Co to přineslo

  • Čas na expedici jedné objednávky: z ~30 vteřin a tří kliknutí na 1 vteřinu a 1 kliknutí.
  • Čas na celou trasu: z 5+ minut na 2 kliknutí (vystavit + tisk).
  • Žádné duplicitní doklady: transakční izolace to hlídá sama.
  • Automatické dokončení: admin nemusí myslet na to, že když odvezl poslední kus, má objednávku zavřít. Systém to udělá sám.
  • Auditní stopa: každý vystavený dodací list je zalogovaný — kdo, kdy, ke které objednávce.

Technické detaily, které stojí za zmínku

Zjištění připravenostibuildDispatchBoard() bere objednávky, spočítá u každé položky, kolik z ní je na skladě po odečtení už vydaných kusů, a zahodí ty, kde není nic. Výsledkem je seznam objednávek, u kterých má smysl mačkat tlačítko.

Sdílený selectdispatchOrderSelect je definovaný jednou a používají ho deska i issueRound(). Kdyby se změnila struktura dat, obě místa se přizpůsobí kompilací, ne runtime chybou.

Cena bez databázového dotazu — hodnota zboží na řádku se počítá jako availableQuantity × unitPriceGross. Žádný join na ceník, žádná cache. Data už jsou v dotazu na objednávku.

Expediční deska je příklad feature, která sama o sobě není složitá — ale když chybí, provoz se zadrhává na detailech. Jeden přehled, jedno tlačítko, jedna transakce. A najednou sklad jede sám.

// ALL_POSTS
ZPĚT NA BLOG