Bei Shopware 6 ist es etwas anders das Platzhalter-Bild für Produkte ohne Bilder zu ersetzen. An sich ist es ganz einfach, wenn man weiß wie es geht. Auch Anleitungen aus dem Internet von 2019 sind nicht mehr aktuell, denn es geht nun noch einfacher!
DAs Bild ist ein SVG-Icon mit dem Namen 'placeholder'. Man kann mit der Suche schnell herausfinden, wo es überall verwendet wird. Nun muss man nur ein eigenes Icon in per Plugin einbringen. Das ist auch an sich sehr einfach. Man legt einen Pfad in seinem Plugin an "src/Resources/app/storefront/dist/assets/icon/myiconpack/" und kopiert dort seine SVG-Datei rein. Die SVG kann auch gerne als placeholder.svg bennant sein. Nun muss man den Twig-Tag "sw_icon" noch um zwei Werte ergänzen. "pack" ist der oben im Pfad genutzte Icon-Pack Name und der "namesapce" ist einfach der Name des Plugins in dem man sich gerade befindet.
Einfach mal die Meta-Description mit einem Prefix versehen, um kleinere SEO-Probleme zu beheben. Klingt einfach und führt schnell zur Verzweiflung. frontend/detail/header.tpl angepasst und es funktioniert nicht. Keywords ändern ist kein Problem nur die Meta-Description des Artikels will sich nicht ändern.
Lösung Wie man schnell vermutet wird die eigene Änderung überschrieben. Der Schuldige ist das SEO-Plugin und da muss man die index.tpl anpassen. Die Änderung kann z.B. so aussehen:
Während ja jede Entity eine id hat, hat nicht jede ein name-Field. Ich musste doch etwas suchen bis ich erstmal die Dokumentation zu der Component gefunden habe und dann mir klar war wie ich ein andere Feld angeben kann. Am Ende natürlich genau so wie man es sich dachte nur der Prop-Name ist irgendwie seltsam, weil Label ja auch anders dort verwendet wird als Begriff.
Ich musste mir eine sw2-Datenbank anlegen über root/root, aber an sich sollte es wie in meinem SW5-Environment auf mit der sw-Datenbank und sw/sw gehen.
Ich hatte für meinen Blog leider die letzten Monate wenig Zeit. Viel zu tun bei der Arbeit, daneben und auch noch ein Homeoffice einrichten inkl. Renovierung. Deswegen hier in einer sehr kurzen Form was ich so mal sagen wollte:
* API-Platform: mähhh... ein Springboot finde ich besser und flexibler
* Next.js: React für PHP-Developer... so mag ich React
* Shopware und Translations: s_article_tranlsations ist nur Caching und s_core_translations ist alt und nicht hübsch
* Bootstrap 5: endlich da und sieht aus wie Bootstrap 4 in noch besser (kein JQuery mehr!)
* Sunlu SPLA: totaler Schrott gefühlt... nicht stabil genug
* Webcams: 300%ige Preissteigerungen.. ich war/bin beeindruckt
* Confluence: Ich habe lange mit geweigert und überlegt, aber nun nutze ich auch Confluence für mich selbst
* Cyberpunk 2077: Gutes Spiel, aber gerade aus Entwicklersicht sehr interessant. wie der Release lief und wie die QK/QS doch grandios in einigen Fällen scheitern kann
* Angebot vor paar Wochen - 150 EUR 4K Samsung Monitor: Super für den Preis, zerkratzt leider leicht, ideal für das Homeoffice
* DECT Thermostate: lohnt sich sowas und würde es im Homeoffice(-Dachgeschoss) wirklich Kosten sparen? China Wifi Lösungen sind zwar günstiger aber die AVM-Lösung sieht für mich besser aus, weil es keine Cloud braucht. Integration mit Alexa?
Manchmal möchte man Artikel für 0.00 Euro anbieten. Zum Beispiel irgendwelche Muster-Artikel.
Wenn man nun einfach einen Preis von 0.00 Euro eingibt, gibt es einige seltsame Nebeneffekte. Der Warenkorb funktioniert auch nicht mehr und man findet im Log den Fehler (sBasket::getPriceForAddProduct):
Man kann einen Update-Endpoint für Bestellungen definieren, der angesprochen, wenn sich der Status einer Bestellung bei EDC-Wholesale ändert. Problem ist, dass das nicht immer funktioniert und Hilfe auch nicht immer kommt. Aber man hat Mail die man parsen kann. Die wichtigsten Regex dafür sind:
Betreff:
/Auslieferung Ihrer Bestellung/
Body-Trackingcode:
/>(\d+)<\/b>/
Body-Ordernumber:
/Kontrollnummer:<\/b>\s*(\d+)<br/
Damit kann man alle Daten bekommen und seine Bestellungen updaten. Mit einer Shopware 5 Lösung kann ich gerne auf Anfrage helfen.
Gerade bei den Shopware Plugins war es wichtig, dass sich die Funktionsweise eines Plugins nicht einfach ändert. Ich hab erlebt was passieren kann, wenn sich bei einen Newsletter-Plugin in einer Minor-Version plötzlich das Verhalten änderte. Es wurden plötzlich alle Kunden mit Gruppe übertragen und nicht mehr nur die einer bestimmten Gruppe und weil niemand sich die Änderungen groß angesehen hat, bekamen plötzlich alle den Newsletter. Das war doof.
Kaum jemand liest den Changelog oder nur selektiv nach bestimmten Fixes. Wenn ich als Shopware-Betreiber einen Fix für ein Plugin brauche, habe ich meistens keine Zeit jedes Verhalten und jede Funktion des Plugins nochmal durch zu testen. Ich will nur mein Problem beheben und keine neuen bekommen. Jedes neue Feature muss also in den default Einstellungen erst einmal deaktiviert sein. Nur Fixes dürfen ungefragt greifen.
Funktionen im Beta-Stadium dürfen auch nicht aktivierbar sein, wenn sie nicht stable sind. Unfertiger-Code darf mit ausgeliefert werden, wenn es nicht anders geht! ABER er muss auch hart im Code deaktiviert sein. Oder auch im Build-Prozess aus der Config-XML auskommentiert werden. Da muss dann aber sichergestellt sein, dass geprüft wird, ob das Config-Value überhaupt gesetzt ist und wenn nicht, dass es als deaktiviert gilt.
Aber wenn es nicht zwingend nötig ist, sollte man unfertige Features nie mit ausliefern und per Feature-Flag deaktivieren. Am Ende wird es sicher noch mal überarbeitet und es funktioniert dann anders. Weglassen minimiert das Risiko auf Fehler und dass irgendwer es doch aktiviert und nutzt.
Fazit: Feature-Flags sind wichtig, aber nicht dafür da um unfertige Features mit ausliefern zu können (ein Feature pro Feature-Branch!!!!)
Aus Shopware kennt man das Prinzip, dass man beim Erweitern von Templates einfach "parent:" angeben muss und es wird immer das vorher gehende Template mit dem selben Pfad erweitert. So kann man ein Template mehrmals durch eine unbekannte Anzahl von Plugins erweitern lassen. Twig will aber immer einen Namespace haben. Also muss man heraus finden, mit welchen Plugin man anfängt und welches Plugin dann auf das aktuelle folgt oder man auf das Basis-Template gehen muss. Ich hab mich von der Shopware 6 Implementierung inspirieren lassen und ein kleines Beispiel gebaut, bei dem man die ein Template erweitern kann und die Plugin-Namespaces immer dynamisch ergänzt werden.
Die Verzeichnisstruktur des Beispiels ist sehr einfach:
Die Basislogik habe ich in einer einfachen Function zusammen gefasst. Hier wird entweder das Plugin heraus gesucht mit dem angefangen werden muss oder das Plugin, das auf das aktuelle folgt und auch dieses Template mitbringt.
function findNewBase($template, $list = [], $currentBase = null) {
$result = 'base';
$found = $currentBase == null; //if null, took the first one
foreach($list as $key => $path) {
if($key == $currentBase) {
$found = true;
}
else if ($found && file_exists($path . '/' . $template)) {
$result = $key;
break;
}
}
return $result;
}
Die Integration wird über ein Token-Parser implementiert.
final class ExtTokenParser extends AbstractTokenParser {
/**
* @var Parser
*/
protected $parser;
private $list = [];
public function __construct(array $list)
{
$this->list = $list;
}
public function getTag(): string
{
return 'base_extends';
}
/**
* @return Node
*/
public function parse(Token $token)
{
$stream = $this->parser->getStream();
$source = $stream->getSourceContext()->getName();
$template = $stream->next()->getValue();
$stream->injectTokens([
new Token(Token::BLOCK_START_TYPE, '', 2),
new Token(Token::NAME_TYPE, 'extends', 2),
new Token(Token::STRING_TYPE, $parent, 2),
new Token(Token::BLOCK_END_TYPE, '', 2),
]);
Jetzt fehlt nur noch eine passende include-Funktion und man kann sich selbst ein System bauen, desen Templates sich über Plugins ohne Probleme erweitern lassen. Ich arbeite daran....
Edit: Die vollständige Implementierung mit extends und include ist jetzt auf GitHub zu finden.
Plugins für Shopware 6 zu schreiben ist an sich garnicht so unterschiedlich zu dem selben Vorgang für Shopware 5. Was nur anders geworden ist, dass Shopware eigene XMLs und Enlight-Components durch den Composer und Symfony abgelöst wurden. Man kann nun wirklich Namespaces definieren, die unabhängig vom Plugin-Namen sind und es ist kein Problem mehr den Composer mit Plugins zu verwenden, sondern es ist jetzt der zu gehende Weg.
Setup
Wie gehabt kann man sein Plugin unter custom/plugins anlegen.
custom/plugins/HPrOrderExample
Zu erst sollte man mit der composer.json anfangen.
Wichtig in der composer.json ist einmal der PSR-4 Namespace den man auf das src/ Verzeichnis setzt und der "extra" Teil. Dort wird alles definiert, was mit Shopware zu tun hat. Hier wird auch die zentrale Plugin-Class angegeben, die nicht mehr wie in Shopware 5 den selben Namen des Verzeichnisses des Plugins haben muss. Auch sieht man hier dass der Namespace-Name nichts mehr mit dem Verzeichniss-Namen zu tun hat.
Wir könnten durch die composer.json das Plugin auch komplett außerhalb der Shopware-Installation entwickeln, weil die gesamte Plattform als Dependency eingebunden ist. Deswegen müssen wir auch einmal den Composer ausführen.
php composer install
Plugin programmieren Nun fangen wir mit dem Schreiben des Plugins an.
Die Plugin-Klasse enthält erst einmal keine weitere Logik:
<?php
namespace HPr\OrderExample;
use Shopware\Core\Framework\Plugin;
class OrderExample extends Plugin{
}
Wir wollen auf ein Event reagieren. Dafür benutzen wir eine Subscriber-Klasse. Während man diese in Shopware 5 nicht unbedingt brauchte und alles in der Plugin-Klasse erldedigen konnte, muss man nun eine extra Klasse nutzen und diese auch in der services.xml eintragen. Die Id des Services ist der fullqualified-classname des Subscribers
Resources ist vordefiniert, kann aber in der Plugin-Klasse angepasst werden, so dass diese Dateien auch in einem anderen Package liegen könnten.
src/Subscribers/OnOrderSaved
<?php
namespace HPr\OrderExample\Subscribers;
use Shopware\Core\Checkout\Order\OrderEvents;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class OnOrderSaved implements EventSubscriberInterface {
/**
* @return array The event names to listen to
*/
public static function getSubscribedEvents()
{
return [
OrderEvents::ORDER_WRITTEN_EVENT => ['dumpOrderData']
];
}
public function dumpOrderData(EntityWrittenEvent $event) {
file_put_contents('/var/test/orderdump.json', json_encode($event->getContext()));
}
}
Hier sieht man, dass nur noch Symfony benutzt wird und keine Shopware eigenen Events.
Wir schreiben einfach die Order-Entity, die gespeichert wurde in eine Datei. Dort können wir UUID und ähnliches der Order finden.
Das ganze kann man dann weiter ausbauen und auf andere Order-Events (die man alle in der OrderEvents.php finden kann) reagieren.
Installieren
Damit ist das Plugin auch schon soweit fertig. Den Composer haben wir schon ausgeführt (ansonsten müssen wir es spätestens jetzt machen).
Nun geht es über die UI oder die Console weiter. Dort wird das Plugin installiert und und aktiviert.
Wenn man nun eine Bestellung ausführt wird, deren Entity in die oben angegebene Datei geschrieben.
Der einfachste Weg einen Shopware Shop zuinstallieren war immer, die ZIP-Datei mit dem Webinstaller downzuloaden und diese in das gewünschte Verzeichnis zu entpacken. Dann die URL aufrufen und dem Installer folgen. Das ist für automatische Deployments nicht so toll und oft wurde dort einfach die Git-Version verwendet. Die hat den extremen Nachteil, dass diese unglaublich viele Dinge mitbringt, die in einer produktiven Umgebung nichts zu suchen haben. Buildscripte, Tests, etc bringen einen wirklich nur in der Dev-Umgebung was und sollten in der produktiven Umgebung nicht mit rumliegen, weil je mehr rumliegt, desto mehr Sicherheitslücken oder ungewollte Probleme könnten mitkommen.
Seit einiger Zeit kann man Shopware aber auch sehr einfach über den Composer installieren. Dabei wird eine eher moderne Verzeichnisstruktur angelegt und auch die Basis-Konfiguration kann einfach über Env-Variablen gesetzt werden, so dass ein automatisches Deployment für einen Server damit sehr einfach wird. Im Idealfall hat man die Datenbank schon sauber und fertig vorliegen. Dann erspart man sich fast den gesamten Installationsprozess und kann direkt loslegen.
Wenn man den Composer noch nicht installiert hat, muss man diesen kurz installieren:
<Directory /var/www/your_webshop>
AllowOverride All
Require all granted
</Directory>
RewriteEngine On
</Virtualhost>
Reload des Apache und schon kann es an sich losgehen. Wenn man sehen möchte wie die DATABASE_URL verarbeitet wird, kann man einen Blick in die etwas komplexer gewordene config.php werfen die man nun unter your_webshop/app/config/config.php findet.
Sollte man noch keine fertige Datenbank auf dem Server liegen haben, muss man die ./app/bin/install.sh ausführen. Gerade für mehrere automatische Deployments, würde ich aber die Datenbank einmal local auf meiner Workstation anlegen und mit Default-Werten befüllen. Diese kommt dann auf den Datebankserver und wird beim deployment, mit den spezifischen Daten wie den Shopdaten und Admin-Zugängen versehen.
Natürlich würden Updates auch über den Composer laufen, wobei sw:migration:migrate automatisch mit aufgerufen wird, um die Datenbank mit aktuell zu halten. Das Verhalten kann man über die Deaktivierung des entsprechenden Hooks in der composer.json verhindern (aber das macht an sich nur in Cluster-Umgebungen Sinn). Ein Update über das Webinstaller-Plugin würde Probleme bereiten und sollte, wenn man es dann ,z.B. weil man eine alte Installation umgezogen hat, installiert und aktiv hat mit ./bin/console sw:plugin:uninstall SwagUpdate entfernen.
Der wirkliche Vorteil liegt jetzt darin, dass man in die composer.json seine Git-Repositories von den Plugins mit eintragen kann und die Plugins direkt über den Composer installieren und updaten kann. Man muss also nicht diese erst vom Server downloaden + entpacken oder per Git clonen (wo dann wieder viel Overhead mit rüber kommen würde).
Ein Update auf 5.6 ist schnell und einfach gemacht. Alle Tests liefen super und es sah nach einer 2 Minuten Aktion aus... dann kam das SwagPaymentPaypal und nutzte Shopware::VERSION. Um genau zu sein in der Bootstrap.php in Zeile 146. Das gibt es in 5.6 aber nicht mehr und deswegen lief dann auch gar nichts mehr. Das Plugin lies sich auch nicht deaktivieren.
Wer also das Plugin noch nutzt sollte es unbedingt durch SwagPaymentPayPalUnified ersetzen. Das alte muss deinstalliert werden, weil die selben Config-Namen verwendet werden.
Wer das alte noch hat und in das selbe Problem läuft, muss dann leider die Zeile 146 per Hand ändern. In etwas wie das hier:
if (false) {
Darin wird etwas für Shopware < 4.2.0 gemacht, was wir mit 5.6 eindeutig nicht mehr brauchen.
Es kann dazu kommen, dass beim Aufruf der console von Shopware 5 keine kernel.environment gesetzt ist. Bei Webserver Aufrufen wird diese ja zumeist in der conf des Apache gesetzt.
Um jetzt die Environment (z.B. "production") direkt beim Aufruf der console zu setzen muss man die Option "e" setzen und schon geht alles.
Jeder der ein Plugin verwendet, das viele Daten hin und her schaufelt und dieses auch immer protokolliert, kennt das Problem von zu großen Log-Files. Gerne hätte man schon immer Log-Files für bestimmte Anwendungsfälle geschrieben. Mit 5.6 ist es jetzt möglich wenigstens pro Plugin ein eigenes Log-File zu nutzen.
Ich habe gerade die Early Access Version von Shopware 6 installiert und erst einmal einen Hersteller und einen eigenen Artikel angelegt.
Das Problem war, dass ich keine Scale-Unit mit angeben konnte, da ich einfach noch keine angelegt hatte und nicht alle Artikeldaten nochmal neu eingeben wollte habe ich das Feld einfach leer gelassen. Speichern klappte ohne Probleme.
Die Kategorie funktionierte dann aber nicht mehr.
Zum Glück war mir das ja direkt aufgefallen, dass ich da was nicht eingeben konnte und habe erst einmal versucht eine Unit anzulegen und zu ergänzen.
Danach funktionierte es dann auch alles.
Da muss man also aufpassen, weil fehlende Eingaben noch viel kaputt machen können. Hab da auch gleich ein Issue dafür aufgemacht.
Hier einmal eine Default-Config für ElasticSearch unter Shopware 5. Gerade mit der Composer-Installation ist es sehr praktisch alle benötigten Felder direkt mal zu sehen.
Manchmal hat man einfach Scripts von anderen, die mit einem Fehlercode beendet werden und es ist vollkommen ok. Leider bricht bei so einem Fehler dann der Build-Process vom Docker-Image ab. Deswegen muss man den Fehlercode überschreiben.
RUN composer create-project shopware/composer-project my_shop --no-interaction --stability=dev; exit 0
Damit wird der Fehler wegen fehlender .env-Datei übergangen. Da man diese Datei an sich auch nicht braucht.
Was ich zur Plugin-Entwicklung mit Shopware 6 nach dem ersten Kennen lernen bei der Shopware AG am 1.7. zu sagen habe:
- Varianten sind nun Produkte mit Parent
- Ids in der Datenbank sind nun UUUIDs (ramsey/uuid) die binär in der DB gespeichert werden
- Ein Plugin definiert sich allein durch eine composer.json und den darin enthaltenen Typ shopware-platform-plugin (ersetzt die plugin.xml) und PSR-4 Namespace und Plugin-Class werden darin definiert
- die Metadaten zum Plugin sind in der composer.json unter extra - Changelog ist jetzte die CHANGELOG.md Datei und mit ISO-Lang Postfixes erhält man die verschiedenen Sprachen (soll wohl mal durch den Store ausgelesen werden können)
- ./bin/console plugin:create --create-config MySW6Plugin erstellt einen ein komplettes Plugin Skeleton mit Config-XML und passender composer.json
- src/Resources/views/ wird automatisch registriert (wenn man seine Views wo anders liegen hat kann man getViewPath überschreiben)
- in der Plugin-Config kann man card-Tags verwenden, um Blöcke zu bilden und alles besser strukturieren zu können
- man kann eigene Felder und View in die Plugin-Config einbringen, in dem man vor eine Vue-Component registriert hat und diese dort als <component name="MyComponent"/> angibt
- Plugin installieren: ./bin/console plugin:install --activate --clear-cache MySW6Plugin öfters ist ein refresh der Liste nötig und cache:clear sichert den liebgewonnen Workflow bei der Entwicklung mit Shopware :-)
- Es gibt kein Doctrine-ORM mehr sondern die eigene DAL die noch DBAL verwendet
- DAL ist so etwas wie ein halb fertiges ORM wo noch die Abstraktion wie Annotations fehlt und man deswegen die Beschreibung und Umwandlungen selbst per Hand vornehmen muss, so wie auch das Anlegen der DB-Tables
- Es gibt dort noch viel zu verbessern, aber das Grundgerüst funktioniert da schon sehr gut. Zu verbessern wäre z.B.: new IdField('id', 'id') auch als new IdField('id') schreiben zu können wenn DB-Column und Class-Field gleich bezeichnet sind
- die Field-Definition-Methode wird nur selten aufgerufen, deswegen kann man hier sonst auch eigene Lösungen wie CamelCase-Converter oder Annotion-Parser selbst integrieren
- Es gibt momentan immer 3 Klassen: Definition, Plain Old PHP Object und Collection (über @Method der eigenen Collection wird so etwas wie Generics versucht abzubilden... in Java wäre es nur List<MyEntity> list = ....)
- Es wir automatisch ein Repository für jede Entity erzeugt, das man mit Hilfe der service.xml injecten kann
- CLI, Subscriber, etc werden weiterhin über die service.xml verwaltet und implementieren nur andere Interfaces (sonst hat sich dort kaum was verändert)
- Es gibt keine Hooks mehr sondern nur noch Service-Decoration (wie die meisten wohl schon jetzt lieber verwendet haben) und eben die Events
- Es gibt mit der DAL ein Versions-Feld
- Änderungen an der Datenbank sollten über das Migration-System (Timestamp basiert) erledigt werden und nicht über die Lifecycle-Methoden des Plugins
- Es gibt Updates und destructive Update, die die Kompatibilität der DB zu älteren Plugin-Versionen zerstören. Beides kann getrennt ausgeführt werden (2 Phasen Update für Cluster-nodes)
- Die DAL unterstützt native Translated-Fields
- Extensions von Entities werden serialisiert in der DB gespeichert (ALTER TABLE, Extension, Serializer-Klasse, etc). Es gibt keine Attributes mehr.
- Gespeichert wird immer per Array (von Entity-Arrays) und es wird nicht das Object zum Speichern übergeben
- upsert (update or insert) ist da das neue merge - Der Cart hat nun LineItems, die wiederum Children haben können
- LineItems sind nun vollkommen unabhängig von Produkten, es gibt nur noch ein Type und Enrichment-Handler, die Namen und Preise für diesen Type an das Item ran dekorieren
- ... damit kann man verschiedene Bestell-Modi wie "kaufen" und "ausleihen" pro Produkt zusammen in einem Warenkorb abbilden
- Die API stellt automatisch generierte Swagger-Beschreibungen bereit
- Es gibt mehrere APIs so dass nicht nur Datenspflege bestrieben werden kann, sondern auch komplette Bestellprozesse damit abgebildet werden können (damit kann man Shopware als Backend-Lösung für In-App-Käufe oder POS-Systeme verwenden)
- Dadurch kann man Shopware auch headless verwenden (ohne Storefron und Admin, oder nur mit einem davon)
- ein Product aht ein SalesChannelProduct... man kann dort bestimmt auch eine Stock-Quantity hinzufügen und so einen Sales-Channel abhängigen Lagerbestand implementieren (Jeder POS auf einen Event oder in Disco könnte ein eigener Sales-Channel werden und dann kann man genau
monitoren wo Getränke oder Verbrauchsmaterial zu neige geht und reagieren bevor die Mitarbeiter überhaupt merkten, dass in 15min wohl die Strohhalme ausgehen werden)
Insgesamt macht Shopware 6 einen wirklich sauberen und durch dachten Eindruck. Nur die Migrations brauchen noch etwas und die DAL könnte gut einen weiteren Layer drum herum gebrauchen (ich wäre für Annotations an der Entity-Klasse)
Danach sollte auf localhost:8000 der Shop starten.. wenn eine MySQL Auth.-Method Fehlermeldung kommt hilft die Container zu beenden. In der Docker-Compose das DB-Image von dem MySQL-Image auf das MariaDB-Image zu ändern und noch mal die Container neu zustarten und ein wieder install durchführen.
Dann sollte alles laufen und man kann sich unter localhost:8000/admin mit admin - shopware anmelden.
Wenn man die lokalen psh.phar-Aufrufe durch die direkten Docker-Befehle ersetzt, sollte es auch unter Windows funktionieren. Innerhalb des Containers kann man natürlich wieder psh.phar verwenden.
Update: manchaml kann auch helfen, dieses vorher einmal auszuführen, bevor man den Container startet und dort install aufruft:
Subscriber bei Shopware-Plugins sind immer so ein Problem. Sie nehmen Daten manipulieren diese und reichen sie an den nächsten Subscriber weiter. Wenn ein Subscriber in einen Fehler läuft und z.B. leere Daten in die Arguments zurück schreibt, ist am Ende alles kaputt und die Daten sind verloren gegangen. Das ist mir jetzt einmal passiert und deswegen, bin ich darauf umgeschrieben, dass Subscriber nur noch auf einer Kopie arbeiten
dürfen.
Sollte nun etwas schief gehen und die Daten verloren gehen, werden einfach die Ursprungsdaten weiter verwendet. Wenn nun zusätzliche Daten nicht dazu dekoriert werden, ist es zwar doof und man muss in das Log-File gucken, ob Fehler auftraten, aber der Ablauf funktioniert weiter und es kommen immer hin die Grunddaten weiter dort an wo sie hin sollen.
Gerade bei Exports oder Imports mit kritischen und unkritischen Daten zusammen, ist es immer besser wenigstens die kritischen Daten sicher zu haben als gar nichts zu haben. Unkritische Daten kann man meistens dann sogar per Hand nachpflegen.
Ich habe ein älteres (letztes Jahr September) angefangenes aber nie fertig gewordenes Shopware Plugin nun bei GitHub hochgeladen, in der Hoffnung, dass noch jemand was damit anfangen kann. Es fügt z.B. ein zusätzliches Feld zum Product-Slider Emotion-Element hinzu und ändert das Template für bestimmte Artikel im Slider.
Ein Problem bei Smarty 3 ist, dass man zwar sehr schön Blöcke überschreiben oder erweitern kann, es aber keine Möglichkeit gibt, die im Block vorhanden anderen Blöcke dan unangetastet zu lassen. Man muss in so einem Fall die nested Blöcke immer mit kopieren und mit dem Parent-Template immer wieder bei Änderungen abgleichen. Wenn z.B. ein Template einen Block mit einer <form> hat und darin dann die ganzen Eingabe-Möglichkeiten auch als Blöcke ausgelegt sind, muss man wenn man die Form-Action ändern will zwingend auch das ganze Form-Content Templating mit übernehmen.
Eine Möglichkeit das zu umgehen ist den Block rendern zu lassen und dann per String-Operationen das Ergebnis anzupassen. Primitiv aber auch sehr effektiv!
Falls man Formular von Shopware in anderen Contexts als dem forms-Controller einsetzt, kann es nötig sein, dass Template so anzupassen, damit es noch auf den forms-Controller zeigt und nicht auf den Controller in desen Context man gerade arbeitet.
Damit ist es auch schon erledigt und man kann anfangen Seiten der Gruppe zu zuordnen und sie werden entsprechend gerendert. Unterseiten und ähnliches sind auch möglich, wie frontend/index/footer-navigation.tpl zeigt. Die Gruppen findet man in der Datenbank in der Table s_cms_static_groups.
Eigene SEO-Urls kann man mit Shopware relativ einfach hinzufügen, wenn man die technischen Urls kennt. So kann man auch einem Blog-Post eine zweite SEO-Url anlegen, die nicht direkt als Blog-Url erkennbar ist. Diese Urls sind keine Weiterleitungen mit 301 oder 302 sondern echte Urls, die den selben Content liefern, wie die originalen Urls, was bei SEO zu beachten ist.
Wenn man sich für Shopware ein Plugin kauft, kann es sein, dass man die Daten genau so vorfindet, wie man sie braucht, aber möchte das dort verwendete Template ersetzen oder die Daten in einem Template verwenden, das im Plugin noch gar nicht vorgesehen war.
Dafür kann man sich ein eigenes kleines Plugin schreiben. Das geht in 5 Minuten. Wir schreiben uns das Plugin TESTNotLoggedIn und blenden damit den Newsletter in der Footer Navigation aus.
Ins Verzeichnis TESTNotLoggedIn kommt die Datei TESTNotLoggedIn.php:
namespace TESTNotLoggedIn;
use Shopware\Components\Plugin;
class TESTNotLoggedIn extends Plugin{
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_PostDispatchSecure_Frontend' => 'addTemplateDir',
'Enlight_Controller_Action_PostDispatchSecure_Widgets' => 'addTemplateDir',
];
}
public function addTemplateDir(\Enlight_Controller_ActionEventArgs $args)
{
try {
$args->getSubject()->View()->addTemplateDir($this->getPath() . '/Resources/views/');
}
catch(\Exception $e){
//TODO
}
}
}
jetzt kommt das Verzeichnis Resources/views/ dazu und die Datei plugin.xml:
<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/5.2/engine/Shopware/Components/Plugin/schema/plugin.xsd">
<label lang="de">TEST Not Logged In</label>
<label lang="en">TEST Not Logged In</label>
Hier wird das Plugin, das wir erweitern wollen, angegeben. Ich verwendet das kostenlose "Globale Kunden Smarty-Variablen". Nun können wir einfach unsere Templates im views-Verzeichnis anlegen und vorhandene erweitern.
Als Beispiel kommt hier Resources/views/frontend/index/footer-navigation.tpl
Damit ist das Plugin schon fertig und kann installiert werden. Der Newsletter Bereich im Footer ist nun nur sichtbar, wenn man eingelogged ist.
Das Plugin "Eigenschaften in Artikel-Listing" bietet z.B. auch an in der Detailseite die Daten nur als Smarty-Variable bereit zu stellen, damit man selbst direkt die Darstellung implementieren kann und nicht noch eine vorhandene unpassende anpassen oder entfernen und ersetzen muss.
Ich habe jetzt fast genau ein Jahr Shopware-Plugins programmiert und im Community-Store von Shopware angeboten. Es hat mir immer viel Spass gemacht und gerade der Kontakt mit den Kunden war oft sehr interessant. Da es aber über mein Nebengewerbe lief, war die Zeit, die ich dafür verwenden konnte schon sehr eingeschränkt und auch Marketing war nicht so einfach da mit unter zu bringen. Zusätzlich benötigte das eigene Haus immer mehr Zeit. Es ist schon ein riesiger Unterschied, ob man eine Mietwohnung hat oder ein eigenes Haus, was den Zeitbedarf angeht.
Daher hatte ich mich entschiedenen meine Nebentätigkeiten herunter zu fahren. In dem Zuge kam ich an den Punkt, wo mir klar wurde, dass weniger nichts bringt, sondern ich ganz aufhören muss.
Die laufenden Projekt wollte ich aber nicht so einfach wegwerfen, besonders weil ja auch Kunden darauf vertrauen, dass deren Plugins und andere Software weitergepflegt wird und Fixes erstellt werden.
Deswegen wechseln meine Plugins nun zur Windeit Software GmbH, was mir die Möglichkeit gibt, den Plugins auch die Zeit und Investionen zukommen zu lassen, die sie benötigen. Es ist also nur zum Vorteil aller, denn jetzt wird es geben:
- verbesserten Support
- besseres Marketing
- richtige Dokumentation (Videos?)
- Urlaubsvertretung
- alles noch professioneller
In der nächsten Zeit werden also alle Plugins umgestellt. Kunden werden einen neuen Namen und ein neues Logo sehen, aber der Rest bleibt gleich und alle Kaufe und Subscribtions laufenden unverändert weiter.
Und was werde ich jetzt machen? Haus, Garten und im Blog wird es wieder vermehrt um Development, Arbeit, Hardware-Projekte und kleine private Projekte gehen. Weniger Content, aber dafür etwas persönlicher wieder mit mehr Inhalt.
Wenn man ein eigenes Haus hat, hat man auch genug mit IT und Infrastruktur Themen zu tun.
Momentan ändert sich bei mir Nebengewerblich einiges und ich überlege wie ich meine Shopware Projekte weiter pflegen kann und vielleicht sogar richtig mit Marketing pushen könnte. Das Problem dabei ist, dass ich gerne kleine Dinge für meine Kunden tue ohne direkt Geld zu verlagen. Damit aber sich eine Zukunft ergibt, müssen, die etwas mehr Geld generieren. Dabei wäre wohl der erste Schritt für die Erstellung von XSLT-Templates Geld zu verlangen. Bei JTL oder anderen Anbietern, dachte ich mir bis jetzt, dass es ja von Vorteil wäre einfach die zu bauen und dann ins Plugin zu integrieren. Damit wäre das Plugin von sich aus irgendwann mehr Wert und es wäre vertrettbar den Preis zu erhöhen.
Ich werde wohl bis Dezember dann das Schreiben von neuen XSLT-Templates kostenpflichtig machen. Pro Template brauche ich so 1-2h, wenn ich dann normale Zeiten wie bei meinem Job hätte wohl eher 1h. Das sind Preise dann die niemanden Weh-tun würden.
Das XML-Plugin bekommt noch einmal ein großes Update oder es wird sogar ein 2. Plugin mit dann aber einem entsprechenden Preis (momentan mein favorisiertes Vorgehen). Das kleine wäre dann für die Anbindung an WWS und ERPs und große wäre dann für echtes Dropshipping.
Wenn ich es schaffe, die auch weiterhin weiter entwickeln zu können, wäre dann auch hoffentlich die Zeit für Dokumentation und Tutorial-Videos.
Ich bin selbst sehr gespannt wie alles weiter geht und wie einige meiner Projekte in Zukunft aussehen werden.
Da es zu dem Thema "Einkaufswelten Elemente selber bauen mit ExtJS" relativ wenig Hilfe bei Shopware gibt, habe ich mich mal wirklich mit [url=https://developers.shopware.com/developers-guide/custom-shopping-world-elements/#advanced:-adding-a-custom-emotion-component-in-extjs]Hilfe von dem Shopware eigenen Tutorial zu Einkaufswelten Elementen[/ur]) und vielen einzelnen Formus-Beiträgen da durch gekämpft. Ich mag ExtJS immer noch nicht, aber so langsam komme ich wenigstens mit den Components klar. Ist am Ende nicht so viel anders wie Swing oder SWT, nur wird in den Constructoren der Componenten die man erweitert sehr viel "Magic" gemacht auf die man angewiesen ist. Allein die Frage: wie bekomme ich ein einfaches kleines Text-Feld in das richtige FieldSet?
Elemente von Einkaufswelten sind ein wirklich nicht so einfaches Thema, wenn man die reine PHP-Schiene verlässt und mit ExtJS arbeitet. Aber diese Mischung im Vimoe-Element Beispiel finde ich jetzt auch nicht so wirklich toll. Felder hier, Stores da.. man sollte alles auf einen Blick haben und wenn man mit JavaScript arbeitet muss man das Plugin auch nicht öfters neu installieren sondern nur den Cache löschen und das Shopware-Backend neu laden.
Init-Methode einer einfachen kleinen GUI mit einem Feld zur Artikel-Auswahl:
initComponent: function() {
var me = this;
me.callParent(arguments);
Darin sieht man wie man einen eigenen Store mit AJAX-Backend definiert und diesen in der ComboBox verwendet, die man wiederum in den Element-Einstellungen anzeigt. Wenn man schon mal soweit ist, steht einen auf dem Weg zu Einkaufswelten mit eigenen Elementen in Shopware nicht mehr viel im Weg.
Dropshipping mit Shopware ist ja nicht immer ganz einfach, wenn man nicht direkt alle Produkte von einem Vendor bezieht. Wenn man nur einen für alles hat ist alles ganz einfach weil man direkt, die gesamte Bestellung an diesen senden kann. Hat man nur einen, aber der auch nur bestimmte Produkte liefert geht es jetzt auch bzw. der erste Testdurchlauf für diesen Use-Case lief sauber durch.
Für Multi-Vendor Orders muss man eine Bestellung in mehrere kleine pro Distributor/Vendor/Lieferant aufteilen. Wenn man nur einmal alle 2h oder so die Bestellungen übermittelt und die Waren an sich selbst liefern lassen möchte, kann man diese sogar noch mal vorher wieder mergen, weil dann Lieferant und Empfänger bei allen Bestellungen immer gleich sind. Aber das ist schon ein Ausnahmefall.
Natürlich kann jeder Vendor einen eigenen Endpoint für die Bestellungen bereitstellen. Entweder per Email, Datei-Upload oder REST-API können die Daten in immer unterschiedlichen XML-Formaten übertragen werden. Das macht dann mehr Probleme.
Mein Shopware-Plugin kann nun aber immerhin verschiedene Vendor/Hersteller bei diesem Splitting ignorieren. Damit ist es nun möglich, wenn man nur einen Vendor für bestimmte Produkte hat, diesen auch mit dem Plugin die Bestellungen automatisch zu zustellen. Bei mehr als einem muss mein Plugin noch leider passen.
Das Plugin kann also momentan folgende Fälle komplett abdecken:
- Übertragung von allen Bestellungen in ein ERP-System oder ähnliches
- Übertragung von allen Bestellungen an einen Vendor (mit oder ohne Dropshipping)
- Übertragung von bestimmten Bestellungen oder auch nur Teil-Bestellungen an einen Vendor (mit oder ohne Dropshipping)
ob es sich um eine einfache Order bei dem Vendor handelt oder um Dropshipping, muss man sich teilweise im XSLT für das Order-XML selbst zusammen bauen (oder mich damit beauftragen). Für die openTRANS-Formate versuche ich momentan eine Lösung zu entwickeln, die dann z.B. die Lieferadresse automatisch nach Konfiguration mit den eigenen Adress- oder den Kunden-Adressdaten befüllt.
Für den Export zulassen
An einer vollständigen Lösung, um beliebig viele Vendor mit jeweils eigenen Übertragungsarten und XML-Formaten anzubinden, arbeite ich auch nebenbei. Gerne würde ich eine Shop-System unabhängige Lösung bauen.. leider fehlt dafür aber Zeit und Geld... bzw. hauptsächlich Geld.. weil wenn man das Geld hat, hat man auch die Zeit.
Einfach mal zwischen durch ein Proof-Of-Life des Shopware Plugin für Cashless-Payment und irgendwann geht es mit dem Shopware Plugin und dem Ganzen auch ganz sicher weiter!
Die RFID-Reader liegen zur Weiterentwicklung bereit.
Der Blog liegt leider bei mir gerade etwas brach. Zu viel zu Arbeiten und auch das Haus zieht viel Aufmerksamkeit auf sich. Das Wetter ist eben so gut, dass man den Garten dann gegen Ende doch fast auf dem Niveau hat, das man sich Anfang des Jahres vorgenommen hat.
Momentan steht ein Projekt mit meinem PHP-Framework, ganz oben auf der Todo-Liste. Wenn ich mal das Blog-Modul und das Admin-Panel verbessern würde, kann man es auch über all da einsetzen, wo sonst eher WordPress eingesetzt wird. Aber ist Dabei modular, MVC, Events, etc... also doch noch eher dazu da darin selbst zu entwickeln.
Die Pflege der Shopware Plugins und die Entwicklerer weiter Shopware Plugins ist momentan auch eher zurück gestellt. Wobei dass das ist, was ich dann gegen Ende des Jahres wieder mehr verstärkt betreiben werden.
Zum Ende des Jahres, werde ich mal wieder mehr schreiben. Auch mal wieder etwas mehr über das Thema Arbeit an sich. Druck, Arbeitszeitmodelle und so, da ich in letzter Zeit doch öfters in nicht immer positiver Weise damit konfrontiert wurde.
Wobei man natürlich auch Garten heutzutage nicht ohne IT auskommt (Temperatur-Überwachung).
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.
Eines meiner Plugins hat jetzt eine Bronze-Zertifizierung. Das ist toll, dass besagt, das das Plugin öfters runter geladen und schon bewertet wurde. Wenn man das geschafft hat man die Zertifizierung aktivieren. Es soll viele Vorteile bringen und helfen das Plugin besser zu verkaufen.
Ich bin gespannt!
In der Zwischenzeit wird mehr Zeit in ein andere Projekt und das XML-Export Plugin investiert bzw. so viel Zeit wie der Garten einen gerade übrig lässt.
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:
Meine Datei
Ohne Anpassung
Mit eigener Anpassung
Die neue Datei angeben und Ausgabeformat anpassen
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?