Oft ist es B2B-Bereich gewollt, dass mögliche neue Kunden das gesamte Sortiment einsehen können, aber noch nicht direkt die Preise angezeigt bekommen.
Nur die registrierten und vom Händler freigegebenen Kunden sollen, dann die Preise sehen können.
Das hat damit zu tun, dass verschiedene Kunden sehr verschiedene Preise sehen können (abhängig von Kundengruppen in die die Kunden einsortiert werden) und ein default-Preis könnte diese Kunden verschrecken oder es soll nicht erkennbar sein, wie viel Rabatt wirklich gegeben wurde oder gegeben werden kann.
Deswegen sollen diese erst eine Anfrage durchführen und sie werden dann in die passende Gruppe einsortiert, die vielleicht 15% weniger Zahlen als die default-Preise.
Mein Shopware-Plugin Kunden-Registrierung blockieren hilft dabei, in dem es Preise für nicht angemeldete Besucher komplett ausblendet und auch verhindert, dass sich Kunden ohne Freigabe oder Anfrage registrieren können.
In den letzten Wochen hat sich doch einiges beim meinem XML-Export Plugin getan. Langsam aber zielstrebig geht es in die Bereiche Dropshipping und Bestellung-Konsolidierung. Dadurch wird es zu einem wichtigen B2B-Baustein.
Mit der Version 0.4.1 ist nun viel mehr in dem Bereich möglich (Veröffentlichung folgt in den nächsten Tagen)
Als erstes Beispiel wie man das Plugin nicht nur dafür verwenden kann für das eigene ERP Bestellungen zu exportieren, habe ich hier erotikgrosshandel.de . Deren Schnittstellen-Doku ist ziemlich gut und sie haben ein sehr minimalistisches Format, so dass man relativ schnell zum Ziel kommt. Die Voraussetzung waren:
- Ein eigenes passendes XSLT-Template (nach deren Doku)
- Der ApiClient muss FORM-Data per Post senden können (nicht nur wie bisher JSON)
- CronJob und CLI-Command müssen den Push an eine API auslösen
- Das Model muss die Lieferanten spezifischen Bestellnummern der Artikel enthalten (Puchase-Preise kamen gleich mit dazu...)
Das ganze war dann eigentlich nur viel Kleinkram und der POST als FORM-Data. War also an sich relativ schnell umgesetzt und lies sich gut testen.
Eingestellt muss sein:
- Format: eigene XSLT-Transformation
- Den Pfad zur eigenen XSLT angeben (absoluter Pfad vom /-Root aus!)
- Export-Pfad ist nicht nötig, aber sollte man doch setzen, falls man als Kontrolle die XML-Daten doch selbst noch mal vorhalten möchte (auch hier der absolute Pfad)
- nie automatisch exportieren (nur per CLI, CronJob oder API), man sollte den CronJob verwenden
- CronJob soll nur Bestellungen mit dem Status 0 und 12 (offen und vollständig bezahlt) verarbeiten: "0,!1,!2,!3,!4,!5,!6,!7,!8,!9,!10,!11,12,!13,!14"
- nach dem Export auf Status 1 (in Bearbeitung) setzen
- Host und URL setzen für erotikgrosshandel.de
- Content-Feldname auf "data" ändern
- Post-Format auf "FORM" setzen, damit in "data" die XML-Daten zu finden sind
Damit sendet er per CLI oder CronJob alle 0+12 Bestellungen im Lieferanten eigenen XML-Format an deren Schnittstelle und setzt danach die Bestellung auf 1+12, damit sie beim nächsten Durchlauf kein weiteres mal übertragen wird.
Den CronJob auf 10min stellen und dann sollten alle 10 Minuten alle "offenen" Bestellungen an den Server übertragen.
Über XSLT-Dateien kann man in anderen Fällen natürlich dann auch andere Formate wie GS1 oder OCI implementieren. Um die Daten zu den anderen Systemen übertragen zu können stehen der API-Client, Emails, Abruf über die Shopware-API oder der Export als Datei (und dann Übertragung per FTP oder SCP) zur Verfügung.
In den nächsten Wochen steht bei mir auf dem Plan, Bestellungen konsolidieren zu können, so dass bei einem CronJob-Lauf alle 2h nicht alle Bestellungen einzeln übertragen werden müssen, sondern auch zusammen gefasst werden können, wenn die Bestellungen für den selben Empfänger vorgesehen sind.
B2B ist anders. B2C ist einfach. Bei B2C macht man Werbung, zeigt Preise und versucht einen möglichst offenen (Paypal-Express) und einfachen Checkout den Kunden zu präsentieren. Bei B2B kommt der Kunde teilweise nicht einfach in den Shop. Da ist der Shop eine Dienstleistung, die den Kunden bereit gestellt wird. Also wird man teilweise erst Kunde und kommt dann in den Shop. Preise sind oft auch so eine Verhandlungssache und nicht jedem Kunden werden sofort Preise präsentiert, weil diese ihn verschrecken könnten, da man mit Abnahmen in kleinen Mengen kalkuliert und Staffelpreise zu ungenau wären, weil hier keine Abnahmen pro Jahr oder so dargestellt werden können. Bloß weil ich 1000 Stück bestelle heißt es nicht, dass ich als B2B-Kunde nicht schon genau weiß, dass ich noch weitere 11000 im restlichen Jahr bestellen werde (aber ich will natürlich Lagerplatz sparen oder nicht Dinge aufwendig gekühlt lagern).
Mein erster Versuch etwas für den B2B-Bereich in Shopware zu entwickeln war mein Plugin zum Verhindern von Kundenregistrierungen. Das klingt nach weniger als es kann, weil es doch sehr fein granulär regelt was bei der Registrierung möglich sein soll:
- Keine Registrierung und nur ein Text mit Infos
- Keine Registrierung aber ein Formular für Anfragen
- Blockieren von bestimmten Email-Adressen
- nur bestimmte Email-Adressen erlauben (Mitarbeiter-Shops und so)
- Nur Firmen als Kunden
- Keine Firmen als Kunden
Damit lässt sich schon mal sehr klar definieren, wer und wie in meinen B2B-Shop darf. Muss ich den Account selbst anlegen für meinen Kunden (dann kann ich direkt festlegen was er sehen darf und was nicht) oder darf es selbst muss aber meine Einstellungen abwarten und darf erst dann kaufen? Das alles kann ich damit steuern.
Ich würde gerne stärker in die Richtung gehen und habe anfangen mir eine recht eigene aber doch nicht so einzigartige, wie Firmen oft glauben, Fantasy-Firma aus zu denken und damit einmal exemplarisch einen Weg zu einem fertigen B2B-Shop zu skizzieren. Die Firma vertreibt noch nicht über einen Shop, hat aber eine entsprechende IT mit ERP und WWS. Die Firma produziert selbst, oft auch nach Bedarf, bietet aber auch einige kleine OEM-Produkte zusätzlich zu ihren Produkten an. Diese OEM-Produkte sind meist kleines Zubehör und Verbrauchsmaterialien.
Als Shopware Edition kommt die CE zum Einsatz, weil die an sich ja alles kann und wenn mehr Support gewünscht ist, kann man ja immer noch nachrüsten. Die B2B-Suite lasse ich aus und gucke, ob man nicht die nötigsten Dinge auch einfacher und günstiger selber schreiben kann und wo hier die Grenzen sind (Workflows mit Freigaben und Hierarchien, wäre hier ein Punkt, wo man echt überlegen sollte, ob man da selbst was schreibt).
Die wichtigsten Punkte sind:
1) Produkt-Daten in den Shop bekommen
2) Bestellungen exportieren
3) Lagerbestände abgleichen
4) Kunden Registrierung
5) Preise und Rechnungen
6) Abrufbestellungen
7) Sets aus Produkten (in Hinsicht auf OEM-Zubehör, das ausgehen könnten)
Also fangen wir mal an die Punkte zu analysieren und einfache + schnelle Lösungen zu finden. Denn eine Time-To-Market sollte auch hier nicht länger als 4-6 Monate benötigen. Was das kostet... darüber kann man vielleicht ganz am Ende noch mal nachdenken. Aber so viel würde ich schon mal sagen: Das Team sollte aus einem Shopware-Entwickler, einen Entwickler aus dem ERP-/WWS-Bereich, jemanden für das Theme + allgemeines Design (Umsetzung kann ja über den Shopware-Entwickler laufen) und jemanden der Kunden-/Preis-/Produktdaten betreut.
1) Produkt-Daten in den Shop bekommen Das ERP sollte an sich schon eine Schnittstelle mitbringen um Produkte zu exportieren, wenn nicht kann man die in 2-3 Tagen realisieren. Ob SAP oder was eigenes, es funktioniert alles ganz einfach und linear. Wird ein Produkt angelegt oder geändert und ist von den Daten her vollständig wird es als Datei oder per JMS ausgegeben (per Änderung-Flag oder direkt Live ist dabei sogar egal).
Wenn noch keine Schnittstelle existiert, wäre es gut, wenn diese direkt auf das Event reagiert und den Content per Push an den nächsten Service weiterreicht oder auch auch direkt im richtigen JON-Format an Shopware sendet.
Am einfachsten lässt sich so etwas über eine Template-Engine realisieren, wie sie für fast jede Umgebung existiert.
Ansonsten kann man sich eine eigene kleine Middlware bauen, die Daten per File-Watcher oder MDB empfang, die Daten auf Objekte mappt, die vom Interface her den Models von Shopware gleichen und dann per JSON-Serialisierung ausgeben und direkt an die Shopware-API sendet.
Nur eine Sache würde ich wirklich an der Shopware-API über ein Plugin ändern: Es sollte egal sein, ob man POST oder PUT verwendet, wenn eine Produktnummer dabei steht und useNumberAsId=true gesetzt ist, sollte die API selbst heraus bekommen.
Also das Plugin nimmt die Nummer und lädt die Id dazu nach. Existiert eine wird diese im Model, das gerade rein kommt, ergänzt und die Anfrage an die PUT-Action weitergeleitet. Existiert keine Id wird zur POST-Action weitergeleitet. Das ist dann genau so wie ein Merge bei einem ORM (Doctrine, JPA). Ich hab so ein Plugin schon mal geschrieben und es ist echt praktisch und beschleunigt die Datenübermittlung sehr, weil nicht erst durch ein GET bestimmt werden muss, ob die Software die Daten
als POST oder PUT senden muss.
Sollte als Schnittstelle OCI oder BMEcat zur Verfügung stehen, sollte man diese nutzen. Einen eigenen API-Controller für diese Formate zu schreiben, geht relativ schnell und unkompliziert. Teilweise kann soet was sogar besser sein, als die vorhandene Shopware-API. Wenn man Standard-Formate nutzen kann, sollte man es tun, dann wäre selbst ein Wechsel es ERP (z.B. von was eigenen auf SAP) mit übersichtlich viel Arbeit möglich und was am Shop ändern zu müssen.
2) Bestellungen exportieren
Genau so wichtig wie der Import und Abgleich der Artikeldaten ist der Rückweg, nämlich der Export von Bestellungen. Eine einfache API-Schnittstelle mit Filter auf den Bestell-Status zu schreiben geht schnell und einfach. Somit kann ein anderes System sich alle Bestellungen mit einem oder mehreren Status/Status aus dem Shop einfach abholen. Die simpelste Variante ist es per Scheduler-EJB oder Cron-Job laufen zu lassen.
Wenn man mehr in Richtung Echtzeit gehen möchte, kann man eine neue Bestellung natürlich auch vom Shop aus mit einem Push-Client an eine REST-API einer Middleware oder eines ERP senden.
Die primitivste Variante ist natürlich, die Bestellung als Datei abzulegen und per FileWatcher dann dort abholen zu lassen.
Wichtig bei dem Vorgang ist der Faktor Zeit. Denn ja schneller alles geht, desto weniger Gefahr besteht, das Bestellungen aus dem Shop und dem ERP einen Konflikt um Lagerbestände auslösen könnten.
Es fing alles mit einem kleinen Plugin zum dumpen von Bestellungen an, dass ich zum Debugen auf dem produktiven Server entworfen habe. Es entwickelte sich weiter zu einem vollwertigen order-Export Plugin und kann nun:
- Export als Shopware-XML, openTRANS 1.0, openTRANS 2.1
- per XSLT kann man auch jedes weiter XML-format erzeugen (OCI sollte so auch gehen.. sollte.. habe leider kein System zum Testen)
- Export als Datei direkt nach der Bestellung
- Export über CLI-Command und Cron-Job (in das Dateisystem)
- Automatisches Status-Update nach dem Export
- Export oder einen REST-API Controller (mit manuellen Status-Update)
- Automatischer Push an eine REST-API (RESTEasy im Wildfly wurde getestet)
Mit den ganzen verschiedenen Wegen und Formaten, ist eine Integration in vielen Fällen möglich. Man benötigt weiterhin einen Entwickler, da mit die Gegenseite korrekt eingebunden werden kann, aber wenn man die Kommunikation erst einmal laufen hat, läuft alles sehr stabil und zuverlässig (eine Version läuft seit Nov. 2017 und hat bis jetzt nie Probleme verursacht).
Bestellungen sind zum Glück immer sehr einfach strukturiert und sind seit Jahrzehnten auch fast unverändert geblieben.
Was das Plugin nicht zur Verfügung stellt sind allgemeine Updates beider Status und die Übermittlung von Trackingcodes. Hier ist aber die REST-API die Lösung des Problems. Da die Nummer der Bestellung beim Export mit übermittelt wurde, kann man anhand dieser die Bestellung über die REST-API laden und modifizieren.
Wenn man das fertige Plugin verwendet, ist der Aufwand eher gering und man kann sich eher um die Update-Aktionen, die vom ERP aus getriggert werden kümmern.
Momentan entwickelt es sich auch zu einem Dropshipping-fähigen Plugin, mit dem man Bestellungen, für sich oder für seinen Kunden, direkt an externe Lieferanten und Großhändler weiterreichen kann.
3) Lagerbestände abgleichen Lagerbestände abzugleichen ist mit das komplexeste Thema bei der ganzen Anglegenheit. Während bei den Artikeln sich nur ein System in eine Richtung synchronisieren muss, geht es hier in beide Richtungen. Denn Bestellungen können sowohl über den Shop als auch das ERP ausgelöst werden und manchmal fällt im Lager auch einfach was runter.
Das schlimmste was passieren kann ist, dass man mehr verkauft als man hat. Im einfachsten Fall muss der Kunde länger warten bis man selbst nachbestellt oder nach produziert hat. Im schlimmsten Fall muss man dem Kunden sagen, dass er die bestellte Ware nicht bekommen kann.
Das eigentliche Problem bei den Lagerbeständen ist, dass alles asynchron und nebenläufig ist. Während im ERP eine Order gespeichert wird, wird auch der aktuelle Lagerbestand bestimmt und 2 Bestellungen aus dem Shop liegen in der Import-Queue (alles mit dem selben Artikel). Wenn der Shop jetzt einfach den da bestimmten Lagerbestand anzeigen würde, wäre bis zum nächsten Abgleich der Lagerbestand wieder um 2 höher, weil der schon durch die Bestellungen abgezogene Bestand bei der Neuberechnung nicht verbucht war, der Shop diese 2 Bestellungen aber ab sich schon kennt.
Hier muss mit Differenz-Buchungen und Timestamp gearbeitet werden. Eine sehr gute Lösung ist das führen einer Lagerbewegung-Chain, die durch Absolute-Lagerbestände in Blöcke auf geteilt wird. Man rechnet immer ab dem neusten Absolute-Eintrag aufwerts.
Beispiel:
-1 order 136 2018-01-01 12:15:00 (exportiert)
-1 order 135 2018-01-01 12:14:59 (exportiert)
-1 order 134 2018-01-01 12:02:00 (exportiert und importiert im ERP)
+8 absolute 2018-01-01 12:00:00
Bestand: 5 im Shop
Es ist gerade 12:15:10 und der Lagerbestand im ERP bestimmt und in den Shop importiert, die beiden Orders brauchen noch 15s bis deren Import beginnt (weil der Austausch über Dateien und Cronjobs läuft). Die letzte Bestandsänderung für den Artikel verzeichnet das ERP für 12:03:10, weil dort die Order von 12:02:00 im ERP verarbeitet wurde.
-1 order 136 2018-01-01 12:15:00 (exportiert)
-1 order 135 2018-01-01 12:14:59 (exportiert)
7 absolute 2018-01-01 12:03:10
-1 order 134 2018-01-01 12:02:00 (exportiert und importiert im ERP)
8 absolute 2018-01-01 12:00:00
Bestand: 5 im Shop
Das ist super! Der Bestand aus Shop-Sicht ist gleich geblieben, weil er in beiden Fällen alle Daten kannte. Es wird nie zuviel verkauft werden, obwohl das ERP noch gerade glaubt 7 auf Lager zu haben, weil es die beiden neusten Bestellungen noch nicht kennt.
Am Ende kann man so schnell wie möglich sein und es wird immer diesen kleinen Bereich geben, wo eie BEstellung gerade unterwegs ist, während der Bestand im ERP berechnet wird.
Wo man diese Differenzverrechnung macht ist nicht ganz so wichtig. Ob in einer Middleware oder im Shop direkt, macht kaum einen Unerschied. Es muss immer der verkaufte Bestand aus den Orders als Differenz verbucht werden und Bestandsmeldungen vom ERP entgegen genommen und gespeichert werden.
Das schöne an dem Prinzip ist auch, dass es sich super erweitern lässt, um zukünftige Bestande über Liefer-Avis und ähnliches. Darüber lassen sich dann wieder Lieferzeiten sehr viel genauer bestimmen, wenn man schon weiß, wann wieder etwas auf Lager sein wird und dann auch wie viel. Wenn ich 10 Stück bestelle und im Avis nur 5 Stück angekündigt sind,muss ich ein Lieferdatum nach der kommenden Lieferung verwenden. Normal bei Shopware hätte man nur die typische Lieferzeit des Suppliers, weiß aber nie ob dann wirklich was da ist und wenn doch wie viel Stück.
Die Implementierung so einer Tabelle und den passenden Queries ist relativ einfach. Events für Orders, um die Verkäufe zu vermerken, sind leicht gefunden. Ein API-Controller, um die Bestandmeldungen vom ERP/der Middleware zu empfangen ist auch nicht viel Arbeit.
Ich habe mal so ein Plugin angefangen und einen schon wirklich sehr vollständigen Prototypen hatt ich nach 3 Abenden.. also wohl so 4-5 Stunden. 1-2 Tage voll daran arbeiten und man bekommt so etwas ohne Probleme hin. Die Zeit zum Testen natürlich nicht mit gerechnet.
Die neue Version vom automatischen XML Order-Export, nutzt für openTrans2.1 XSLT und keinen PHP-Code mehr. Der Vorteil davon ist, dass so für jeden Anpassungen an der Ausgabe sehr viel einfacher werden. Man kopiert sich die vorhandene XSLT-Datei und kann die Kopie so anpassen wie man will. Im Plugin kann man die Datei dann angeben und diese anstelle der originalen XSLT-Datei verwenden.
Wenn man z.B. ein Attribute-Field zusätzlich als offiziellen openTRANS2.1 XML-Tag einbinden:
Ich wollte ja das Order-Export in Richtung XSLT bearbeiten... nun wird alle sehr vollständige Überarbeitung daraus. Besseres Export verhalten nach Änderungen an Bestellungen.. auf über das Backend. Insgesamt soll alles etwas flexibler werden und für mehr B2B-Anwendungsfälle passend gemacht werden.
Andere Projekte gehen auch nicht so wirklich weiter. Das Cashless-Paymentsystem ist erst einmal auf HOLD. Ich habe zwar schon fertige Pläne, um es auch als ganz normale Kasse (mit Barcode-Scanner) nutzen zu können und auch die Bons dokumenten-echt als signierte PDF parallel zum Druck speichern zu können.
Auch wollte ich mal eine wirkliche Präsentation (vielleicht mit Video) dafür basteln. Am Ende könnte ich mit wenig Zeit noch ein gutes und fertiges Produkt daraus machen. Leider fehlt die Zeit oder das Geld um bei der Zeit das Projekt neu zu priorisieren.
Aber erst einmal steht auch wieder Bremen am 30.6. an. Diesmal bewaffnet mit unter anderen 2 ThinkPad T500 für echt wenig Geld (Die sind für mich da auch Verbrauchsmaterial.. bei höheren Stückzahlen einfach mal wegen dem Preis direkt anfragen. Auch wegen den passenden Akkus und WLAN-Karten dazu).
Im Hintergrund bei der Artikelseite läuft mein "Eigenschaften im Listing" Plugin als Dataprovider ohne eigene Ausgabe.
Nach längerer Zeit habe ich auch mal wieder etwas Arbeit in mein B2B Registrierung-Blockieren Plugin gesteckt. Nun ist das Styling und das Layout nicht mehr ganz so hässlich. Man kann etwas mehr einstellen (nur die Form aus einer Liste auswählen macht im Backend echt viel Ärger.. ich habe es nach hinten verschoben).
Update auf Version 2.5 erfolgt in den nächsten Tagen.
Mein Registrierung-Plugin wird in der Version 2.1 ein paar kleine Verbesserungen erhalten. So kann man festlegen, ob die Registrierung nur als Firma oder nur als Privat-Kunde möglich ist (oder natürlich wie sonst auch beides).
Zusätzlich kann man einstellen das Schnellbestellungen als Gast-Kunde nur als Privat-Kunde möglich ist und nicht als Firma.
Die 2.1 Version ist im Store. Momentan wird noch an einem Fix für ein Problem 5.4 RC1 gearbeitet.. der noch mit 5.3.7 getestet werden muss oder wieder fallen gelassen wird, sollte sich 5.4 RC2 in dem Bereich wieder "normal" verhalten.
Blog-entries by search-pattern/Tags:
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?