Przejdź do głównej zawartości

Integracja ze Stockly

Stockly jest zbudowany tak, żeby dało się nim sterować z zewnątrz. Moduł przewoźnika, ERP trzymający wagi, desktopowe narzędzie do etykiet, program obsługujący drukarkę niewidoczną dla serwera, 3PL robiący własną kartonizację, system księgujący towar na półki. Żadne z nich nie potrzebuje zmiany w Stockly, żeby z nim współpracować.

Metody są cztery. Wybór właściwej przesądza o kształcie całej integracji.

MetodaKiedyGdzie działa
Nazwane zdarzeniaTwój kod żyje w tej samej instalacji Shopwarew procesie
Endpointy HTTPTwój kod żyje gdzie indziejprzez Admin API
Encje DALwypychasz dane podstawowe albo czytasz do raportuprzez API albo w procesie
Punkty rozszerzeńdokładasz zdolność: przewoźnika, strategię podziałuw procesie
Twoje zadanieZacznij od
położyć towar w binie, zdjąć go, przewieźćJak położyć zapas w binie
zapytać, co leży na półkachOdczyt zapasu
policzyć, zasilić magazyn, przelać jeden w drugiKorekty i zadania masowe
dowiadywać się, gdy coś się zmieniaZdarzenia
zdecydować coś zamiast StocklyPunkty decyzyjne
masz już stany w innym systemieObce zapisy
wysyłać, pakować, drukowaćta strona i API pakowania

Niezależnie od zadania zacznij od modelu zapasu: prawie każdy błąd integracji w historii tego modułu brał się z niezrozumienia, która liczba wynika z której.

Nazwane napisem, żeby nasłuchujący nigdy nie musiał importować naszej klasy, i taki jest cel tego nazewnictwa: moduł, który psuje się przy braku Stockly, jest zależnością, a nie integracją.

ZdarzenieZnaczenie
p2lab.stockly.parcels.planprosi o propozycję; nic nie zapisuje
p2lab.stockly.parcels.commitzapisuje podział
p2lab.stockly.parcel.trackingzgłasza numer przesyłki dla jednej paczki
p2lab.stockly.parcel.registeredpaczka dostała numer przesyłki
p2lab.stockly.parcel.failedzamówienie przesyłki nie doszło do skutku, z powodem
p2lab.stockly.label.printoddaje gotową etykietę do kolejki druku; odsyła jobId
p2lab.stockly.label.preferred_formatpyta, w jakim formacie zamówić etykietę u przewoźnika
p2lab.stockly.print.job.printedzadanie wydrukowane; argumenty jobId, printerId
p2lab.stockly.print.job.failedzadanie nie powiodło się; argumenty jobId, error

Dwa ostatnie to raporty, a nie pytania: niosą dane w argumentach zdarzenia, jak reszta tej listy, ale nic nie jest z nich odczytywane z powrotem.

Fakty, które moduł ogłasza później, nazywają się odwrotnie, z podkreśleniem po nazwie dostawcy (p2lab_stockly.…), bo to zdarzenia biznesowe Shopware, a nie hooki, na które udziela się odpowiedzi. Obejmują płaszczyzny popytu, zamówień oczekujących, towaru, zapasu, pracy, wysyłki, zwrotów i spójności; mają własny katalog.

Większość faktów jest też dostępna we Flow Builderze, więc sklep może powiesić na nich mail, tag czy zmianę stanu bez pisania kodu.

Wszystkie pod /api/_action/p2lab-stockly/, uwierzytelniane jak każde wywołanie Admin API i pilnowane tymi samymi uprawnieniami co ekrany.

Wysyłkashipping/…

MetodaŚcieżkaCo robi
POSTshipping/parcels/planpropozycja, nic nie zapisuje
POSTshipping/parcels/commitzapisuje podział
POSTshipping/parcels/requestprosi przewoźnika o przesyłki
POSTshipping/parcels/packedzgłasza, że ekran pakowania skończył
POSTshipping/parcels/of-orderzapisane paczki zamówienia
POSTshipping/parcels/{parcelId}/trackingzgłasza numer przesyłki
POSTshipping/parcels/{parcelId}/cancelanuluje jedną paczkę
POSTshipping/parcels/closenic więcej z tego zamówienia nie wyjedzie
GETshipping/profileszainstalowani przewoźnicy i strategie podziału
GETshipping/batches/eligiblepaczki czekające na przekazanie kierowcy
POSTshipping/batches/closezamyka przekazanie; zwraca id zadania
GETshipping/batches/{batchId}/listlista do podpisu przez kierowcę, jako PDF

Dane pakowaniapackaging/…

MetodaŚcieżkaCo robi
GETpackaging/resolveczego system użyje dla tych artykułów, wraz z pochodzeniem
GETpackaging/coveragejak kompletny jest katalog i co poprawić najpierw
GETpackaging/collisionsartykuły zgarniane przez dwie reguły o równej randze
GETpackaging/variancegdzie przewidywanie nie zgadza się z wagą

Drukprinting/…

MetodaŚcieżkaCo robi
POSTprint-jobprzyjmuje gotową etykietę spoza Shopware; zwraca jobId
POSTprinting/print-jobs/nextwydaje stanowisku następne zadanie; null, gdy nie ma pracy
POSTprinting/print-jobs/{jobId}/ackpotwierdza wydruk albo zgłasza porażkę
POSTprinting/jobs/{jobId}/retryponawia zadanie
POSTprinting/jobs/{jobId}/cancelporzuca zadanie
GETprinting/station-devicesczy instalacja ma drukarki obsługiwane przez przeglądarkę
GETprinting/profilesznane typy drukarek i ich formaty

Zwykłe encje Shopware, więc /api/search/… i /api/… działają tak jak wszędzie indziej.

EncjaTrzyma
p2lab_stockly_parceljedna paczka: waga, numer przesyłki, stanowisko, karton, notatki
p2lab_stockly_parcel_lineco jest w paczce
p2lab_stockly_parcel_batchjedno przekazanie kierowcy
p2lab_stockly_packaging_materialkatalog pudeł
p2lab_stockly_packaging_profilezbiorcze reguły pakowania
p2lab_stockly_product_packagingnadpisania per artykuł
p2lab_stockly_packaging_rulejedna reguła na metodę dostawy
p2lab_stockly_printerjedno urządzenie: sposób podłączenia, format, rozmiar etykiety
p2lab_stockly_print_jobjedno zadanie druku: dane, status, próby
p2lab_stockly_print_document_rulektóry dokument Shopware jest przechwytywany i dokąd trafia
p2lab_stockly_print_rulektóra praca co drukuje, dla kogo i z jakim priorytetem
p2lab_stockly_printer_defaulturządzenie domyślne dla stanowiska, operatora albo magazynu
p2lab_stockly_print_medianośniki, którymi urządzenie może być załadowane

Encje magazynowe — półki, biny, ruchy, loty, nośniki — wymienia model zapasu. Czytaj je do woli, ale zapisuj przez endpointy.

Interfejsy do zaimplementowania, rejestrowane tagiem usługi.

InterfejsDokłada
ParcelSplitStrategyInterfacesposób rozkładania towaru na paczki
CarrierProfileInterfaceprzewoźnika: jego limity, produkty i walidacje
CarrierManifestGatewayInterfacepowiadomienie przewoźnika o zamknięciu przekazania
ExtractionEngineInterfacesilnik odczytu dokumentów dla importu

Kolejka druku to ścieżka osobna od wysyłki i wpuszcza trzy różne rodzaje programów. Ekrany opisuje rozdział o druku; tutaj jest to, co musi wiedzieć kod.

p2lab.stockly.label.print przyjmuje gotową etykietę. Dane jadą w argumentach zdarzenia, nie w jego podmiocie, bo tą samą drogą wraca odpowiedź:

$event = new GenericEvent(null, [
'payload' => $labelBytes,
'format' => 'zpl',
'source' => 'AcmeCarrier',
'externalId' => $parcelNumber,
'orderId' => $orderId,
'context' => $context,
]);
$dispatcher->dispatch($event, 'p2lab.stockly.label.print');
// Dopisane przez kolejkę — do zapisu przy przesyłce albo do anulowania zadania.
$jobId = $event->getArgument('jobId');

Przed zamówieniem etykiety u przewoźnika należy zapytać p2lab.stockly.label.preferred_format o format: argumenty role, format (własny domyślny) i context, odpowiedź pod format. To format urządzenia, na które zadanie i tak trafi, więc drukarka używająca ZPL dostanie ZPL zamiast PDF-a przerabianego po drodze.

Zdarzenie nie wypuszcza wyjątku na zewnątrz. Wołający jest w środku zakładania przesyłki u przewoźnika, gdzie numer paczki już został wydany; nieosiągalna drukarka nie może z problemu druku zrobić problemu wysyłki.

POST /api/_action/p2lab-stockly/print-job robi dokładnie to samo co zdarzenie, tyle że przez API:

{ "source": "acme-desktop", "externalId": "1Z999AA10123456784",
"format": "zpl", "payload": "<base64>", "copies": 1,
"orderId": "0189…", "printerId": "0189…" }

Odpowiedź to {"jobId": "…"}. Zamiast payload można podać mediaId. Bez printerId kolejka wybiera urządzenie sama, tak jak dla etykiety z wysyłki.

Drukarka podłączona przez lokalnego agenta albo przez przeglądarkę nie jest osiągalna dla serwera, więc to stanowisko pyta o pracę:

POST /api/_action/p2lab-stockly/printing/print-jobs/next
Authorization: Bearer <token>
Content-Type: application/json
{"stationId": "0189…", "drivers": ["agent"]}

Odpowiedź niesie job i counts. job bywa null i to jest stan normalny: stanowisko pakowania częściej nie ma nic do druku, niż ma. Zadanie zawiera format (zpl, epl, pdf), copies, driver, tyle o urządzeniu, ile trzeba, żeby je odnaleźć (host, deviceUid, usbVendorId, usbProductId), oraz payload w base64: należy go zdekodować i przepisać bajty bez zmian.

Po wydruku:

POST /api/_action/p2lab-stockly/printing/print-jobs/<jobId>/ack
Content-Type: application/json
{"printed": true}
{"printed": false, "error": "Printer offline"}

Trzy rzeczy kosztują tu najwięcej czasu:

Pobranie rezerwuje zadanie. Bez ack stoi na Sending, dopóki nie zwolni go czas z ustawień druku, domyślnie pięć minut.

Zgłoszona porażka jest ostateczna. Zadania przypiętego do jednego stanowiska nie ma kto przejąć, więc kolejka nie ponawia go sama; ponowienie jest decyzją człowieka albo programu wywołującego.

source razem z externalId daje idempotencję. Powtórzone przyjęcie tej samej pary zwraca istniejące jobId zamiast drugiej etykiety, więc ponowienie po upływie limitu czasu jest bezpieczne. Wołający nie odróżni jednego od drugiego i takie jest założenie.

Uprawnienia: p2lab_stockly_print_job:create dla oddania etykiety, p2lab_stockly_print_job:update dla pętli agenta, p2lab_stockly_printer:read dla printing/station-devices.

Null to nie zero. Wszędzie w danych o pakowaniu brak liczby znaczy „pytaj wyżej”, a zero jest twierdzeniem. Wysłanie 0 jako wagi opakowania oznacza, że artykuł jedzie bez opakowania, i nic niżej tego nie poprawi.

Pochodzenie podróżuje razem z liczbami. packaging/resolve podaje nie tylko wartość, ale i jej źródło: product, parent, profile, global albo none. Integracja, która pokazuje none swoim użytkownikom, wyłapuje braki w danych po kilku dniach, a nie po kilku kwartałach.

Do rejestru zapasu się dopisuje, nigdy się go nie poprawia. Wiersz ruchu nie jest edytowany; korekta to nowy wiersz z własnym powodem. Wszystko, co magazyn odnotowuje o tym, dlaczego w binie leżą cztery sztuki, zbudowane jest z wierszy, i dlatego żadnego nie wolno po cichu zmienić.