Zum Inhalt springen

Stockly anbinden

Stockly ist darauf gebaut, von außen gesteuert zu werden. Ein Versandmodul, ein ERP, dem die Gewichte gehören, ein Desktop-Etikettenwerkzeug, ein Programm, das einen für den Server unsichtbaren Drucker bedient, ein Logistikdienstleister mit eigener Kartonisierung, ein System, das Ware auf Regale bucht. Keines davon braucht eine Änderung in Stockly, um damit zu arbeiten.

Es gibt vier Wege hinein. Den richtigen zu wählen bestimmt die Gestalt der ganzen Integration.

MethodeVerwenden, wennLäuft
Benannte EventsIhr Code in derselben Shopware-Installation lebtim Prozess
HTTP-EndpunkteIhr Code anderswo lebtüber die Admin-API
DAL-EntitätenSie Stammdaten schieben oder für einen Bericht lesenüber die Admin-API oder im Prozess
ErweiterungspunkteSie eine Fähigkeit ergänzen: einen Dienstleister, eine Aufteilungsstrategieim Prozess
Ihre AufgabeBeginnen bei
Ware in einen Lagerplatz legen, herausnehmen, bewegenBestand in einen Lagerplatz legen
fragen, was in den Regalen liegtBestand lesen
zählen, ein Lager befüllen, eines in ein anderes entleerenKorrekturen und Massenaufträge
benachrichtigt werden, wenn sich etwas ändertEvents
etwas anstelle von Stockly entscheidenPolicy Hooks
Sie besitzen die Bestandszahlen bereits anderswoExterne Änderungen
versenden, packen, druckendiese Seite und Packing API

Was auch immer die Aufgabe ist, beginnen Sie mit dem Bestandsmodell: Fast jeder Integrationsfehler in der Geschichte dieses Moduls war ein Missverständnis darüber, welche Zahl aus welcher abgeleitet ist.

Über Zeichenketten benannt, sodass ein Listener nie eine Klasse von uns importieren muss, und das ist der Zweck dieser Benennung: Ein Modul, das kaputtgeht, wenn Stockly fehlt, ist eine Abhängigkeit, keine Integration.

EventBedeutung
p2lab.stockly.parcels.planfragt nach einem Vorschlag; schreibt nichts
p2lab.stockly.parcels.commitspeichert eine Aufteilung
p2lab.stockly.parcel.trackingmeldet eine Sendungsnummer für ein Paket
p2lab.stockly.parcel.registeredein Paket hat eine Sendungsnummer erhalten
p2lab.stockly.parcel.failedeine Buchung ist gescheitert, mit dem Grund
p2lab.stockly.label.printübergibt ein fertiges Etikett an die Druckwarteschlange; antwortet mit einer jobId
p2lab.stockly.label.preferred_formatfragt, welches Format beim Dienstleister zu bestellen ist
p2lab.stockly.print.job.printedein Auftrag wurde gedruckt; Argumente jobId, printerId
p2lab.stockly.print.job.failedein Auftrag ist gescheitert; Argumente jobId, error

Die letzten beiden sind Meldungen und keine Fragen: Sie tragen ihre Daten wie der Rest dieser Liste in den arguments des Events, es wird aber nichts daraus zurückgelesen.

Tatsachen, die das Modul im Nachhinein veröffentlicht, sind andersherum benannt, mit einem Unterstrich nach dem Anbieter (p2lab_stockly.…), denn sie sind Shopware-Business-Events und keine Hooks, die zu beantworten sind. Sie umspannen Bedarf, Rückstände, Ware, Bestand, Arbeit, Versand, Retouren und Integrität und haben einen eigenen Katalog.

Die meisten dieser Tatsachen sind auch im Flow Builder verfügbar, ein Händler kann also eine Mail, ein Tag oder einen Statuswechsel daran hängen, ohne dass jemand Code schreibt.

Alle unter /api/_action/p2lab-stockly/, wie jeder Admin-API-Aufruf authentifiziert und durch dieselben ACL-Berechtigungen begrenzt wie die Bildschirme.

Versandshipping/…

MethodePfadWas er tut
POSTshipping/parcels/planein Vorschlag, schreibt nichts
POSTshipping/parcels/commitspeichert eine Aufteilung
POSTshipping/parcels/requestfragt den Dienstleister nach Sendungen
POSTshipping/parcels/packedmeldet, dass der Packbildschirm fertig ist
POSTshipping/parcels/of-orderdie gespeicherten Pakete einer Bestellung
POSTshipping/parcels/{parcelId}/trackingmeldet eine Sendungsnummer
POSTshipping/parcels/{parcelId}/cancelstorniert ein Paket
POSTshipping/parcels/closefür diese Bestellung geht nichts mehr hinaus
GETshipping/profilesdie installierten Dienstleister und Aufteilungsstrategien
GETshipping/batches/eligiblePakete, die auf die Übergabe an einen Fahrer warten
POSTshipping/batches/closeschließt eine Übergabe ab; liefert eine Job-Id
GETshipping/batches/{batchId}/listdas Blatt, das ein Fahrer unterschreibt, als PDF

Verpackungsdatenpackaging/…

MethodePfadWas er tut
GETpackaging/resolvewas das System für diese Artikel verwenden wird, samt Herkunft
GETpackaging/coveragewie vollständig der Katalog ist und was zuerst zu berichtigen ist
GETpackaging/collisionsArtikel, die zwei Regeln gleichen Rangs beide beanspruchen
GETpackaging/variancewo Vorhersage und Waage auseinandergehen

Druckprinting/…

MethodePfadWas er tut
POSTprint-jobnimmt ein fertiges Etikett von außerhalb Shopware entgegen; liefert eine jobId
POSTprinting/print-jobs/nextgibt einem Platz seinen nächsten Auftrag; null, wenn es keinen gibt
POSTprinting/print-jobs/{jobId}/ackbestätigt einen Druck oder meldet einen Fehlschlag
POSTprinting/jobs/{jobId}/retrywiederholt einen Auftrag
POSTprinting/jobs/{jobId}/cancelgibt einen Auftrag auf
GETprinting/station-devicesob diese Installation browsergesteuerte Drucker hat
GETprinting/profilesdie bekannten Druckertypen und ihre Formate

Gewöhnliche Shopware-Entitäten, /api/search/… und /api/… funktionieren also wie überall sonst.

EntitätHält
p2lab_stockly_parcelein Paket: Gewicht, Sendungsnummer, Platz, Karton, Notizen
p2lab_stockly_parcel_linewas im Paket ist
p2lab_stockly_parcel_batcheine Übergabe an einen Fahrer
p2lab_stockly_packaging_materialden Kartonkatalog
p2lab_stockly_packaging_profiledie Verpackungsregeln in großer Zahl
p2lab_stockly_product_packagingÜberschreibungen je Artikel
p2lab_stockly_packaging_ruleeine Regel je Versandart
p2lab_stockly_printerein Gerät: wie es sich anbindet, sein Format, seine Etikettengröße
p2lab_stockly_print_jobeinen Druckauftrag: Nutzdaten, Status, Versuche
p2lab_stockly_print_document_rulewelches Shopware-Dokument abgefangen wird und wohin es geht
p2lab_stockly_print_rulewelche Arbeit was druckt, für wen und mit welcher Priorität
p2lab_stockly_printer_defaultdas Gerät, auf das ein Platz, ein Mitarbeiter oder ein Lager voreingestellt ist
p2lab_stockly_print_mediadie Materialarten, mit denen ein Gerät bestückt sein kann

Die Lagerentitäten — Regale, Lagerplätze, Bewegungen, Lots, Ladungsträger — sind im Bestandsmodell aufgeführt. Lesen Sie sie frei; schreiben Sie sie über die Endpunkte.

Zu implementierende Schnittstellen, über ein Service-Tag registriert.

SchnittstelleErgänzt
ParcelSplitStrategyInterfaceeine Art, Ware auf Pakete zu legen
CarrierProfileInterfaceeinen Dienstleister: seine Grenzen, Produkte und Prüfungen
CarrierManifestGatewayInterfaceeinem Dienstleister mitzuteilen, dass eine Übergabe abgeschlossen ist
ExtractionEngineInterfaceeine Lese-Engine für den Dokumentimport

Die Druckwarteschlange ist ein eigener Weg, getrennt vom Versand, und sie nimmt drei verschiedene Arten von Programmen an. Die Bildschirme behandelt das Druckkapitel; was folgt, ist das, was der Code wissen muss.

Ein Etikett aus einem Plugin derselben Installation

Abschnitt betitelt „Ein Etikett aus einem Plugin derselben Installation“

p2lab.stockly.label.print nimmt ein fertiges Etikett entgegen. Die Daten reisen in den arguments des Events, nicht in seinem Subject, denn die Antwort kommt auf demselben Weg zurück:

$event = new GenericEvent(null, [
'payload' => $labelBytes,
'format' => 'zpl',
'source' => 'AcmeCarrier',
'externalId' => $parcelNumber,
'orderId' => $orderId,
'context' => $context,
]);
$dispatcher->dispatch($event, 'p2lab.stockly.label.print');
// Von der Warteschlange zurückgeschrieben — für einen Nachweis oder um den Auftrag später abzubrechen.
$jobId = $event->getArgument('jobId');

Vor der Bestellung des Etiketts beim Dienstleister ist p2lab.stockly.label.preferred_format zu fragen, welches Format zu bestellen ist: Argumente role, format (die eigene Voreinstellung des Aufrufers) und context, Antwort unter format. Es ist das Format des Geräts, an das der Auftrag geleitet würde, ein Drucker, der ZPL verwendet, bekommt also ZPL statt eines unterwegs gerasterten PDF.

Das Event lässt nie eine Exception entkommen. Der Aufrufer ist mitten dabei, eine Sendung bei einem Dienstleister zu erzeugen, wo bereits eine Paketnummer vergeben wurde; ein unerreichbarer Drucker darf aus einem Druckproblem kein Versandproblem machen.

Ein Etikett aus einem Programm außerhalb Shopware

Abschnitt betitelt „Ein Etikett aus einem Programm außerhalb Shopware“

POST /api/_action/p2lab-stockly/print-job tut genau das, was das Event tut, über die API:

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

Die Antwort ist {"jobId": "…"}. Eine mediaId darf für payload einstehen. Ohne eine printerId leitet die Warteschlange den Auftrag selbst, genau wie bei einem Etikett aus dem Versand.

Ein über den lokalen Agenten angebundener oder vom Browser gesteuerter Drucker ist vom Server nicht erreichbar, der Platz fragt also stattdessen nach Arbeit:

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

Die Antwort trägt job und counts. job ist oft null, und das ist der Normalzustand: Ein Packplatz hat öfter nichts zu drucken als etwas. Ein Auftrag trägt format (zpl, epl, pdf), copies, driver, so viel über das Gerät, wie es braucht, um es wiederzufinden (host, deviceUid, usbVendorId, usbProductId), und payload base64-kodiert: dekodieren und die Bytes unverändert durchschreiben.

Danach:

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

Drei Dinge kosten hier die meiste Zeit:

Das Abholen ist das Beanspruchen. Ohne ein ack sitzt der Auftrag auf Wird gesendet, bis das Zeitlimit aus den Druckeinstellungen ihn freigibt, standardmäßig fünf Minuten.

Ein gemeldeter Fehlschlag ist endgültig. Niemand kann einen an einen Platz gebundenen Auftrag übernehmen, die Warteschlange wiederholt ihn also nicht von selbst; das Wiederholen ist eine Entscheidung für einen Menschen oder für das aufrufende Programm.

source zusammen mit externalId ist der Idempotenzschlüssel. Dasselbe Paar zweimal anzunehmen liefert die bestehende jobId zurück statt eines zweiten Etiketts, ein Wiederholen nach einem Zeitlimit ist also sicher. Der Aufrufer merkt den Unterschied nicht, und das ist beabsichtigt.

Berechtigungen: p2lab_stockly_print_job:create, um ein Etikett zu übergeben, p2lab_stockly_print_job:update für die Agentenschleife, p2lab_stockly_printer:read für printing/station-devices.

Null ist nicht null. Überall in den Verpackungsdaten heißt eine fehlende Zahl „die nächste Ebene fragen”, und eine Null ist eine Behauptung. Eine 0 als Verpackungsgewicht zu schieben besagt, der Artikel werde unverpackt versandt, und nichts weiter unten berichtigt das.

Die Herkunft reist mit den Zahlen. packaging/resolve nennt nicht nur, was ein Wert ist, sondern woher er kam: product, parent, profile, global oder none. Integrationen, die das letzte davon ihren eigenen Nutzern zeigen, finden schlechte Daten in Tagen statt in Quartalen.

Das Bestandsjournal wird beschrieben, nie korrigiert. Eine Bewegungszeile wird nie bearbeitet; eine Korrektur ist eine neue Zeile mit eigenem Grund. Alles, was das Lager darüber festhält, warum ein Lagerplatz vier hält, besteht aus Zeilen, und deshalb darf nichts eine davon stillschweigend ändern.