Čo sa má medzi systémami prenášať
Než sa pustíte do technického riešenia, určte smer toku pre každý typ dát. Toto rozhodnutie je dôležitejšie než výber nástroja, lebo určuje, ktorý systém je pri konflikte nadradený:
- Objednávka — z e-shopu do účtovníctva. E-shop je zdroj pravdy.
- Faktúra — vzniká v účtovnom systéme, do e-shopu sa vracia číslo dokladu a PDF pre zákazníka.
- Skladové množstvo — z účtovníctva do e-shopu. Sklad vie o príjemkách a predaji na predajni, e-shop nie.
- Cenník — väčšinou z účtovníctva, ale pozor na akciové ceny a zľavy, ktoré má e-shop vlastné.
- Karta produktu (názov, popis, fotky) — zostáva v e-shope. Účtovný systém nemá čo prepisovať marketingové texty.
- Úhrada — z bankového výpisu alebo platobnej brány do oboch systémov.
Systémy, s ktorými sa na Slovensku stretnete
Pohoda (Stormware)
Najrozšírenejší účtovný a skladový systém v segmente malých a stredných firiem. Komunikuje cez XML v dokumentovanom formáte. Výmena prebieha buď cez súbory, alebo cez službu bežiacu na serveri, kde je Pohoda nainštalovaná. Praktický dôsledok: integrácia potrebuje sprostredkovateľa na strane klienta, lebo Pohoda je desktopová aplikácia. Formát je stabilný a zvládne aj zložitejšie doklady.
Money S3 a S4 (Seyfor)
Druhý najčastejší systém. Money S3 pracuje s XML výmenou podobne ako Pohoda, vyššia rada S4 ponúka priamejšie rozhranie. Platí to isté obmedzenie — ide o systém bežiaci u klienta, nie v cloude.
SuperFaktúra a iDoklad
Cloudové fakturačné služby s bežným REST API a autentifikáciou tokenom. Integrácia je výrazne jednoduchšia — voláte HTTPS endpoint a dostanete JSON. Ak klient ešte nemá zabehnutý účtovný systém a rieši hlavne fakturáciu, je toto najlacnejšia cesta.
Skladové systémy na mieru
Pri väčších prevádzkach býva sklad samostatný systém, niekedy dvadsať rokov starý, s prístupom cez databázu alebo exportné súbory. Integrácia je potom o dohode na formáte a prenosovom kanáli, nie o API.
Tri spôsoby prepojenia
1. Priame volanie API
Najjednoduchšie, keď druhá strana ponúka REST API dostupné z internetu. Použiteľné pri SuperFaktúre, iDoklade a cloudových skladoch:
$odpoved = Http::withToken(config('services.fakturacia.token'))
->timeout(15)
->retry(3, 200)
->post('https://api.poskytovatel.sk/invoices', [
'client' => [
'name' => $objednavka->odberatel_nazov,
'ico' => $objednavka->odberatel_ico,
'email' => $objednavka->email,
],
'items' => $objednavka->polozky->map(fn ($p) => [
'name' => $p->nazov,
'quantity' => $p->mnozstvo,
'unit_price' => $p->cena_bez_dph_centov / 100,
'tax' => $p->sadzba_dph,
])->all(),
]);
$odpoved->throw();
$objednavka->update([
'cislo_faktury' => $odpoved->json('invoice.number'),
'id_faktury' => $odpoved->json('invoice.id'),
]);
2. Výmena cez XML súbory
Štandardný spôsob pri Pohode a Money. E-shop generuje XML dávku, tá sa preberie na strane klienta a naimportuje. Opačným smerom sa exportujú stavy skladu:
public function exportObjednavok(Collection $objednavky): string
{
$xml = new SimpleXMLElement('<dataPack/>');
foreach ($objednavky as $objednavka) {
$polozka = $xml->addChild('dataPackItem');
$polozka->addAttribute('id', 'OBJ' . $objednavka->id);
$doklad = $polozka->addChild('order');
$doklad->addChild('number', $objednavka->cislo);
$doklad->addChild('date', $objednavka->vytvorene_at->format('Y-m-d'));
// ... hlavička a položky podľa schémy účtovného systému
}
return $xml->asXML();
}
Dôležité je, aby mal každý doklad stabilný identifikátor. Pri opakovanom importe tej istej dávky sa tak doklad neduplikuje.
3. Integračná platforma
Ak systémov pribúda (e-shop, účtovníctvo, dopravca, e-mailing, sklad), oplatí sa postaviť prepojenia cez integračnú platformu namiesto siete priamych spojení. Riešenie cez n8n popisujem v článku o automatizácii firmy cez n8n. Výhodou je prehľad o behoch a možnosť zmeny bez zásahu do kódu e-shopu, nevýhodou ďalší systém, ktorý treba prevádzkovať.
Návrh, ktorý vydrží prevádzku
Všetko cez frontu, nikdy synchrónne
Volanie externej služby priamo počas dokončenia objednávky je chyba. Keď je účtovný systém nedostupný, zákazník uvidí chybu a objednávku nedokončí — hoci s objednávkou samotnou nie je nič zlé. Prenos patrí do fronty:
class OdosliObjednavkuDoUctovnictva implements ShouldQueue
{
public int $tries = 5;
public array $backoff = [60, 300, 900, 3600];
public function __construct(public Objednavka $objednavka) {}
public function handle(UctovnyKlient $klient): void
{
if ($this->objednavka->cislo_faktury !== null) {
return; // už prenesené
}
$vysledok = $klient->vytvorFakturu($this->objednavka);
$this->objednavka->update([
'cislo_faktury' => $vysledok->cislo,
'prenesene_at' => now(),
]);
}
public function failed(Throwable $chyba): void
{
Notification::route('mail', config('integracie.admin_email'))
->notify(new PrenosZlyhal($this->objednavka, $chyba->getMessage()));
}
}
Rastúce odstupy medzi pokusmi (backoff) vyriešia krátke výpadky samé. Metóda failed() zaistí, že o trvalom probléme sa dozviete vy, nie zákazník o týždeň. Podrobnejšie o frontách píšem v článku o Laravel Queues a Jobs.
Mapovanie produktov cez jednoznačný kód
Najčastejší zdroj chýb je párovanie produktov podľa názvu. Názov sa zmení, v jednom systéme má diakritiku a v druhom nie, niekto ho skráti. Párujte výhradne cez kód produktu (SKU), ktorý je v oboch systémoch rovnaký a nikdy sa nemení:
Schema::create('mapovanie_produktov', function (Blueprint $tabulka) {
$tabulka->id();
$tabulka->foreignId('produkt_id')->constrained();
$tabulka->string('externy_kod')->unique();
$tabulka->string('externy_system', 50);
$tabulka->timestamp('synchronizovane_at')->nullable();
$tabulka->timestamps();
});
Samostatná mapovacia tabuľka sa vyplatí aj vtedy, keď máte na začiatku len jeden externý systém. Pridanie druhého potom nie je prepis, ale nový riadok.
Skladové množstvo: rozdiel medzi fyzickým a dostupným
Sklad hlási počet kusov na sklade. E-shop ale musí odpočítať to, čo je v rozpracovaných objednávkach — inak predáte ten istý kus dvakrát:
public function dostupneMnozstvo(Produkt $produkt): int
{
$rezervovane = PolozkaObjednavky::query()
->where('produkt_id', $produkt->id)
->whereHas('objednavka', fn ($q) => $q->whereIn('stav', ['nova', 'zaplatena']))
->sum('mnozstvo');
return max(0, $produkt->mnozstvo_sklad - $rezervovane);
}
Pri vyššom obrate nastavte aj bezpečnostnú rezervu — posledný kus radšej nepredávajte, kým sa stav nepotvrdí. Nedodaná objednávka stojí viac než jeden nepredaný kus.
Rátajte s tým, že import zlyhá v polovici
Prenos dávky sto objednávok, ktorý spadne na päťdesiatej, nesmie zanechať systém v nejasnom stave. Každú objednávku prenášajte samostatne a stav si zapisujte. Po obnovení sa pokračuje tam, kde sa skončilo:
Objednavka::query()
->whereNull('prenesene_at')
->where('stav', 'zaplatena')
->orderBy('id')
->chunkById(50, function ($davka) {
foreach ($davka as $objednavka) {
OdosliObjednavkuDoUctovnictva::dispatch($objednavka);
}
});
Prevádzka: bez dohľadu to nefunguje
Integrácia je živá vec. Externý systém zmení formát, vyprší token, klient premenuje skladovú kartu. Bez dohľadu sa o tom dozviete až vtedy, keď účtovníčka na konci mesiaca zistí, že chýba tridsať faktúr.
- Denný kontrolný súhrn — počet prenesených dokladov, počet zlyhaní, najstarší neprenesený záznam.
- Upozornenie pri fronte, ktorá rastie — to je najskorší príznak, že druhá strana nereaguje.
- Log každého volania vrátane odoslaného tela a odpovede, s obmedzenou dobou uchovania.
- Ručné spustenie prenosu pre jednu objednávku, dostupné v administrácii. Ušetrí to hodiny telefonátov.
Ako takýto dohľad postaviť lacno, popisujem v článku o monitoringu PHP aplikácie.
Zhrnutie
- Pre každú hodnotu určte jediný systém, ktorý ju vlastní — obojsmerná synchronizácia rovnakej hodnoty je zdroj konfliktov
- Produkty párujte cez nemenný kód (SKU), nikdy cez názov
- Každý prenos riešte cez frontu s opakovaním a rastúcimi odstupmi
- Prenos musí byť idempotentný — opakovaný import nesmie vytvoriť druhú faktúru
- Rozlišujte fyzické a dostupné skladové množstvo
- Zaveďte denný kontrolný súhrn a upozornenia na zlyhania
- Do administrácie pridajte tlačidlo na ručné opakovanie prenosu