V slovenskom e-commerce prostredí patrí prepojenie internetového obchodu s účtovným či ERP systémom (napríklad Pohoda, KROS, SAP alebo Helios) medzi najkritickejšie a zároveň najviac zanedbávané miesta celého podnikania.
Väčšina firiem začína s jednoduchým monolitickým e-shopom, na ktorý nainštaluje hotový synchronizačný plugin. Všetko vyzerá v poriadku až do momentu, keď začnú rásť objemy objednávok alebo príde silná predajná kampaň.
Zrazu sa začnú diať nevysvetliteľné veci: objednávky miznú z prenosového radu, faktúry sa v účtovníctve vygenerujú s nesprávnymi sumami, skladové zásoby sa nezhodujú a zákazníci dostávajú tovar, ktorý už nie je fyzicky na sklade. Finančný riaditeľ (CFO) a prevádzkový manažér trávia hodiny manuálnym párovaním platieb a ručným prepisovaním chýbajúcich položiek.
Tento technický článok rozoberá, prečo synchrónne prepojenia a lacné hotové pluginy zlyhávajú pri prenosoch dát a ako v technologickom štúdiu nolimeo navrhujeme robustný, asynchrónny Node.js middleware. Funguje ako bezpečná colnica medzi e-shopom a ERP a výrazne znižuje riziko stratených alebo nevalidných transakcií.
1. Jadro problému: Prečo bežné synchrónne pluginy strácajú dáta?
Lacné e-shopové pluginy fungujú na princípe synchrónnej HTTP komunikácie. Keď zákazník klikne na tlačidlo „Dokončiť objednávku“, e-shop v reálnom čase nadviaže spojenie s API rozhraním vášho lokálneho ERP systému, odošle dáta a čaká na odpoveď, aby mohol zobraziť ďakovnú stránku.
Tento naivný prístup so sebou nesie tri vážne architektonické riziká:
Riziko A: Problém zlyhania synchrónneho volania (Single Point of Failure)
Ak má lokálne ERP účtovníctvo na strane firmy čo i len krátkodobý výpadok, napríklad z dôvodu preťaženia internej siete, plánovanej údržby servera alebo výpadku internetu v sídle firmy, e-shop nedostane odpoveď. Synchrónny plugin v takom prípade buď úplne zablokuje checkout a zákazník neodíde s dokončeným nákupom, alebo objednávku označí za dokončenú, no dátový balík s faktúrou nemusí doručiť správne.
Riziko B: Nízka priepustnosť a databázové zámky
Účtovné systémy neboli navrhnuté na to, aby spracovávali desiatky zápisov za sekundu. Tradičné ERP systémy často používajú staršie relačné databázy s prísnym uzamykaním tabuliek pri zápise (row-level / table-level locking). Keď na e-shope prebieha marketingová kampaň a 10 zákazníkov naraz vytvorí objednávku, synchrónne požiadavky na zápis faktúr sa nahromadia.
E-shop čaká, kým ERP postupne spracuje a uzamkne jednotlivé riadky. Výsledkom je dramatický nárast času odozvy servera (TTFB) na checkout stránke, zlyhania z dôvodu vypršania časového limitu (504 Gateway Timeout) a stratené konverzie.
Riziko C: Absencia dátovej sanitácie a validácie
Rôzne platformy ukladajú adresy a telefónne čísla v rozdielnych formátoch. Ak zákazník do poľa pre PSČ zadá písmená, alebo jeho telefónne číslo obsahuje nepovolené znaky, hotový plugin tieto dáta pošle do ERP bez akejkoľvek úpravy. Staršie účtovné systémy pri stretnutí s neočakávaným formátom dát zlyhajú a celú XML dávku odmietnu zapísať. Výsledok? Objednávka je na e-shope úspešne zaplatená, no účtovníctvo o nej vôbec nevie.
2. Architektonické riešenie: Node.js Middleware ako "Bezpečná colnica"
V nolimeu riešime prepojenie e-shopu s podnikovým ERP vybudovaním izolovaného, asynchrónneho Node.js integračného middleware. E-shop (napr. na báze headless Medusa.js) nekomunikuje s ERP systémom priamo. Všetky dátové toky prechádzajú cez dedikovanú asynchrónnu vrstvu.
┌────────────────────────┐ (Asynchrónne) ┌────────────────────────┐
│ Headless Storefront │ ───────────────────────> │ Node.js Middleware │
│ (Next.js) │ │ - Zod Validácia │
└────────────────────────┘ │ - Redis Front (Queue)│
│ └────────────────────────┘
▼ (Zápis v DB) │
┌────────────────────────┐ ▼ (Asynchrónny zápis)
│ PostgreSQL Databáza │ ┌────────────────────────┐
│ (Stavy objednávok) │ │ Podnikové ERP │
└────────────────────────┘ │ (Pohoda, KROS, SAP) │
└────────────────────────┘
Ako funguje asynchrónna integrácia v praxi?
- Rýchla odozva (non-blocking): Keď zákazník dokončí objednávku, e-shop ju zapíše do svojej lokálnej PostgreSQL databázy a používateľovi vráti úspešné zobrazenie bez čakania na ERP.
- Zaradenie do transakčného radu: Na pozadí systém vygeneruje dátový payload objednávky a asynchrónne ho odošle do middleware, kde sa uloží do perzistentného radu úloh riadeného pomocou Redis (BullMQ).
- Validácia v middleware: Middleware skontroluje štruktúru dát. Ak zistí chybu (napr. neplatné IČO alebo chýbajúce PSČ), dáta nesmerujú do ERP na okamžité zlyhanie. Objednávka sa podrží v middleware a administrátor dostane upozornenie, aby dáta upravil bez zbytočného rizika straty.
- Asynchrónne doručenie s retry mechanizmom: Middleware sa pokúša doručiť objednávku do ERP. Ak ERP neodpovedá, systém nevyhlási fatálnu chybu. Úloha zostáva v Redis a automaticky sa spúšťa proces opakovaných pokusov (
exponential backoff) s narastajúcim časovým odstupom.
3. Implementácia databázového queue modelu v Drizzle ORM
Pre asynchrónne riadenie a možnosť kedykoľvek dohľadať stav prenosu jednotlivých objednávok a faktúr využívame prísne typovanú databázovú vrstvu v middleware. Nižšie je príklad schémy v Drizzle ORM pre evidenciu prenosového radu úloh:
// src/db/schema/syncQueue.ts
import { pgTable, uuid, varchar, integer, jsonb, timestamp, pgEnum } from "drizzle-orm/pg-core";
// Stavy synchronizačného radu
export const syncStatusEnum = pgEnum("sync_status", ["pending", "processing", "completed", "failed"]);
export const erpSyncQueue = pgTable("erp_sync_queue", {
id: uuid("id").primaryKey().defaultRandom(),
orderId: varchar("order_id", { length: 100 }).notNull(),
invoiceNumber: varchar("invoice_number", { length: 50 }),
payload: jsonb("payload").notNull(), // Kompletný JSON payload objednávky odosielaný do ERP
status: syncStatusEnum("status").default("pending").notNull(),
retryCount: integer("retry_count").default(0).notNull(),
maxRetries: integer("max_retries").default(5).notNull(),
errorMessage: varchar("error_message", { length: 1000 }),
syncedAt: timestamp("synced_at"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
Vďaka tomuto modelu máme nad dátovými tokmi výrazne lepšiu kontrolu. Ak synchronizácia zlyhá, v tabuľke vidíme jej pôvodný JSON payload, počet neúspešných pokusov a konkrétnu chybovú správu, ktorú vrátilo API účtovného softvéru.
4. Dátová sanitácia a Zod validácia pred prenosom do ERP
Jedným z najčastejších dôvodov, prečo lacné pluginy zlyhávajú, sú nevalidné vstupné dáta od používateľa. V Node.js middleware implementujeme prísnu typovú kontrolu pomocou validačnej knižnice Zod. Vďaka tomu do ERP odchádzajú iba dáta, ktoré prešli kontrolou formátu a povinných polí.
Nižšie je ukážka validačnej schémy pre fakturačné údaje slovenskej alebo českej firmy:
// src/lib/validation/erpPayload.ts
import { z } from "zod";
// Validácia položky faktúry
export const ERPInvoiceItemSchema = z.object({
sku: z.string().min(1, "SKU označenie je povinné"),
name: z.string().max(100, "Názov položky nesmie presiahnuť 100 znakov"),
quantity: z.number().int().positive("Množstvo musí byť celé kladné číslo"),
unitPriceInCents: z.number().int().nonnegative("Jednotková cena nesmie byť záporná"),
vatRate: z.number().nonnegative("Sadzba DPH nesmie byť záporná"), // napr. 20
});
// Validácia celého fakturačného balíka pre ERP
export const ERPInvoicePayloadSchema = z.object({
orderId: z.string().min(1),
companyName: z.string().min(2, "Názov spoločnosti je príliš krátky"),
ico: z.string().regex(/^\d{8}$/, "IČO musí mať presne 8 číslic"),
dic: z.string().regex(/^(SK|CZ)?\d{10}$/, "Neplatný formát DIČ").optional(),
icDph: z.string().regex(/^SK\d{10}$/, "Neplatný formát IČ DPH").optional(),
street: z.string().min(3, "Ulica a číslo sú povinné"),
city: z.string().min(2, "Mesto je povinné"),
zip: z.string().transform((val) => val.replace(/\s+/g, "")).pipe(
z.string().regex(/^\d{5}$/, "PSČ musí obsahovať 5 číslic bez medzier")
),
items: z.array(ERPInvoiceItemSchema).min(1, "Faktúra musí obsahovať aspoň jednu položku"),
totalAmountInCents: z.number().int().positive(),
});
export type ERPInvoicePayload = z.infer<typeof ERPInvoicePayloadSchema>;
/**
* Otestuje a transformuje prichádzajúce dáta pred odoslaním do účtovníctva
*/
export function sanitizeAndValidateInvoice(rawData: any): { success: true; data: ERPInvoicePayload } | { success: false; error: string } {
const result = ERPInvoicePayloadSchema.safeParse(rawData);
if (!result.success) {
// Agregujeme chyby do zrozumiteľného reťazca pre interný logovací systém
const errorMessage = result.error.errors.map(err => `${err.path.join(".")}: ${err.message}`).join(", ");
return { success: false, error: errorMessage };
}
return { success: true, data: result.data };
}
Všimnite si transformáciu pri PSČ: Zod automaticky očistí vstup od nadbytočných medzier (transform) pred samotnou regex validáciou. Týmto spôsobom predchádzame zbytočným chybám, keď zákazník zadá PSČ s medzerou (napr. 811 01), čo by bežný neupravený import v ERP mohol zamietnuť.
5. Riešenie chybových stavov a Dead Letter Queue (DLQ)
Softvér na strane ERP systémov niekedy zlyhá z dôvodu interných chýb databázy alebo nesprávne uzamknutej účtovnej knihy. Vtedy API vráti stavový kód 500 Internal Server Error. Asynchrónne riešenie sa s tým vyrovnáva nasledovne:
Automatický opakovaný pokus (Retry Mechanism)
Keď BullMQ zistí, že ERP vrátilo chybu 500, automaticky presunie úlohu do stavu delayed a naplánuje opätovné spustenie. Používame exponenciálne predlžovanie intervalu (Exponential Backoff) s náhodným rozptylom (jitter):
-
- pokus: o 5 sekúnd.
-
- pokus: o 30 sekúnd.
-
- pokus: o 5 minút.
-
- pokus: o 30 minút.
-
- pokus: o 2 hodiny.
Veľká časť dočasných výpadkov lokálnej siete alebo reštartov servera sa vyrieši v priebehu prvých pokusov bez zásahu obsluhy.
Presun do Dead Letter Queue (DLQ)
Ak synchronizácia zlyhá aj na 5. pokus, chyba je pravdepodobne logického charakteru (napríklad účtovné obdobie v ERP je už uzamknuté a nepovoľuje zápis nových faktúr k danému dátumu). Úloha sa presunie do Dead Letter Queue (DLQ).
Presun do DLQ automaticky spustí dve reakcie:
- Notifikácia na Slack/SLA kanál: Senior vývojár a prevádzkový manažér klienta dostanú automatickú správu s ID objednávky a technickou príčinou zlyhania.
- Kontrolované uskladnenie: Payload zostáva zapísaný v PostgreSQL databáze synchronizačného radu. Po vyriešení problému v ERP (napr. otvorenie účtovného obdobia) vieme v admin paneli spustiť manuálnu revalidáciu a hromadné preklopenie DLQ úloh bez ručného prepisovania celej dávky.
Záver: Investujte do infraštruktúry, nie do záplat
Prepojenie e-shopu s podnikovým účtovníctvom je chrbtovou kosťou celej obchodnej prevádzky. Spoliehať sa pri väčších objemoch iba na lacné synchrónne pluginy je zbytočné riziko. Každá stratená objednávka, každá nesprávne vyfakturovaná položka a každá hodina strávená manuálnym prepisovaním dát poškodzuje vaše podnikanie a oberá vás o zisk.
V technologickom štúdiu nolimeo staviame robustný softvér pre stredné a väčšie firmy. Sme seniorné boutique štúdio: neponúkame lacné krabicové riešenia a klienti komunikujú priamo s ľuďmi, ktorí systém navrhujú a vyvíjajú. Získavate headless architektúru s dohodnutou SLA podporou a technickou kontrolou nad kritickými dátovými tokmi.
Chcete preveriť bezpečnosť a priepustnosť prepojenia s účtovným systémom alebo plánujete prechod na stabilný asynchrónny middleware? Napíšte nám a prejdeme si ERP integráciu, riziká aj bezpečný technický návrh.
