Korrekte Modifikation des SQL Query beim Filter-Event Shopware_Modules_Basket_AddArticle_FilterSql

Hallo und schönen Sonntag,

Für die Modifikation der Preisberechnung von Artikel, muss ich neben dem Preisberechnungsservice auch noch die Berechnung beim Hinzufügen des Produktes in den Cart modifizieren. Dafür nütze ich den Filter-Event 

**Shopware\_Modules\_Basket\_AddArticle\_FilterSql**

An diesen Filter wird folgender SQL Query übergeben, für den ich entsprechend die Preise modifizieren muss.

$sql = "
            INSERT INTO s_order_basket (id, sessionID, userID, articlename, articleID,
                ordernumber, shippingfree, quantity, price, netprice,
                datum, esdarticle, partnerID, config)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
        ";

Die ? werden dann durch entsprechende Parameter in der sBasket Klasse ersetzt. Mein Problem ist nun, dass ich noch keinen schönen Weg gefunden habe um hier den Preis zu manipulieren. Ersetze ich bei den Values die ? für price und netprice durch manipulierte Werte, führt das natürlich zu einer Exception, da es mehr Parameter als Parameter-Platzhalter gibt.

Hier meine Methode, die den Event konsumiert und den Query modifiziert:

/**
 * Due to the fact that the cart is on the old system and uses an own price calculation
 * algorithm and not the price_calculation_service, we have to emit the event
 * Shopware_Modules_Basket_AddArticle_FilterSql and manipulate the SQL query
 * which is executed each time a product is added to the cart.
 *
 * @param \Enlight_Event_EventArgs $args
 * @return string
 */
public function basketAddArticleFilterSql(\Enlight_Event_EventArgs $args) {


    // Get orderNumber of article for order_basket id
    $article = ($args->get('article'));
    $orderNumber = $article['ordernumber'];

    // Get the shop context
    $context = $this->container->get('shopware_storefront.context_service')->getContext();

    // Use the productService to get the product with prices calculated by the
    // price_calculation_service which is decorated by this plugin.
    $productService = Shopware()->Container()->get('shopware_storefront.product_service');
    $product = $productService->get($orderNumber,$context);

    $cheapestPrice = $product->getCheapestPrice();

    // Add some dummy prices here
    $price = $cheapestPrice->getCalculatedPrice();
    $netPrice = $cheapestPrice->getRule()->getPrice();


    // @TODO Fix SQL Statement

    // Manipulate the query so that the correct price is set on the statement
    $query = "
        INSERT INTO s_order_basket (id, sessionID, userID, articlename, articleID,
            ordernumber, shippingfree, quantity, price, netprice,
            datum, esdarticle, partnerID, config)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, "+$price+", "+$netPrice+" , ?, ?, ?, ? )
    ";
    // -> Causes exception due to wrong parameter count

    return $query;

}

Hat jemand eine Idee, wie ich die Manipulation hier am Besten anstelle??

Danke & LG
Synonymous

Deine Preise werden durch das folgende sUpdateArticle() doch sowieso wieder überschrieben - oder nicht?! Ich würde den insert komplett ignorieren und beim updaten ansetzen.

Viele Grüße

Nein, das geht deshalb nicht, weil die Update auch während des Checkout-Prozesses aufgerufen wird. Das wiederum würde bedeuten, dass sich der Preis (theoretisch) zwischen dem Zeitpunkt wo der Kunde auf der Checkout Seite ist und dann tatsächlich bestellt ändern könnte.

Aber selbst dort gibt es das gleiche Thema. Ich hatte es bereits in der Update implementiert, allerdings über einen hässlichen Workaraound indem ich die Spalten einfach doppelt angegeben hatte.

Hier die Lösung:

Ersetzt man sämtliche Parameter-Platzhalter im Query durch absolute Werte, funktioniert das Ganze wieder ohne Exception. 
Hier anhand des Beispiels für das Update Statement:

https://github.com/synonymous1984/SynonymousPriceCalculator/blob/master/SynonymousPriceCalculator.php#L73

Dennoch muss ich noch einmal nach dem Sinn fragen: dein insert wird durch das direkt folgende sUpdateArticle() doch direkt wieder überschrieben?

Viele Grüße

Dafür gibt es den NotifyUntil Event, über den man das Update unterbinden kann wenn ein bestimmter Controller (hier der add to cart controller) aufgerufen wurde.

Genau das meine ich ja von Anfang an. Ärger dich nicht mit dem insert rum, sondern aktualisiere den Warenkorb, wenn du das event im update abfängst. Würde ich zumindest für die sauberere Methode halten :wink:

Viele Grüße

1 „Gefällt mir“

Nein - wir reden aneinander vorbei :slight_smile:
Die sUpdateArticle Methode wird sehr häufig aufgerufen - und genau das möchte ich verhindern, da der Preis sonst während des Checkoutprozesses modifiziert wird - was ja eigentlich nicht sein darf.

Muss direkt mal versuchen wie sich Shopware hier stadardmäßig verhält, wenn man den Preis während des Checkouts ändert…

Hier noch ein anderer Trick den ich schon manchmal gesehen habe:

$sql = "
            INSERT INTO s_order_basket (id, sessionID, userID, articlename, articleID,
                ordernumber, shippingfree, quantity, price, netprice,
                datum, esdarticle, partnerID, config)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, 3.1415 /* ? */, ?, ?, ?, ?, ? )
        ";

So kannst du den manipulierten Wert direkt einfügen und der Platzhalterwert landet im Kommentar. 

Sauber finde ich das aber nicht.

1 „Gefällt mir“

Ah… guter Mann - der kommt in die Suppe :)
Ich habe es jetzt mit dem kompletten Ersetzen der Parameter gelöst (hier die Update Methode):
https://github.com/synonymous1984/SynonymousPriceCalculator/blob/master/SynonymousPriceCalculator.php#L106

Hi,

genau sowas machen wir im SwagUserPrice-Plugin. Ihr müsst grundsätzlich “nur” die entsprechenden Price-Gateways oder den ProductList-Service dekorieren (damit die Preise in der Storefront modifiziert werden) und dazu das Event Shopware_Modules_Basket_getPriceForUpdateArticle_FilterPrice nutzen. Das liegt (wie Aqua schon meinte) in der sUpdateArticle-Methode, in der 5.2. sollte die aber nicht mehr so arg oft aufgerufen werden. Wenn ihr viele individuelle Preise habt und die in einem Rutsch bearbeiten wollt, habt ihr natürlich das Problem, dass das Event jeweils nur einen Preis modifiziert und euch entsprechend zwingt, auch nur jeweils einen individuellen Preis aus der DB abzufragen. In solchen Fällen würde ich vor Laden des Warenkorbes schon alle individuellen Preise für den Warenkorb des Nutzers selektieren und in einer Property vorhalten. Dann könnt ihr beim Überschreiben der einzelnen Preise prüfen, ob ihr den Wert schon vorgeladen habt - und ggf. darauf zugreifen. In der Praxis skaliert das dann stabil und ihr braucht nur einen Query um N individuelle Preise im Warenkorb abzufragen. Also ganz grob:

 protected $basket = [];

    public static function getSubscribedEvents()
    {
        return array(
            'Shopware_Modules_Basket_getPriceForUpdateArticle_FilterPrice' => 'onUpdatePrice',
            'sBasket::sGetBasket::before' => 'updateBasket'
        );
    }

    /**
     * Pre-fetch 
     */
    public function updateBasket()
    {
        $this->basket = $this->getBasketPrices();
    }



    public function onUpdatePrice(EventArgs $args)
    {
        $basket = $args->getReturn();
        $basketId = $args->get('id');
        $quantity = $args->get('quantity');
        $userId = Shopware()->Session()->offsetGet('sUserId');

        if (!$userId) {
            return $basket;
        }

        $userPrice = isset($this->basket[$basketId]) ? $this->basket[$basketId] : null;
       
        if (!$userPrice) {
            return $basket;
        }


        if (!empty($userPrice)) {
            $basket["price"] = $userPrice;
        }

        return $basket;

    }

    private function getBasketPrices()
    {
       // Hier muss dein benutzerspezifischer Preis selektiert werden. In der s_order_basket
       // steht ja die ordernumber des Artikels, die CustomerID nimmst du aus der Session

        $sql = 'SELECT id, price FROM s_order_basket YOUR JOIN CONDITION WHERE sessionID = :session';
        return Shopware()->Db()->fetchPairs($sql, ['session' => Shopware()->SessionID()]);
    }

In der getBasketPrices müsst ihr also entsprechend anpassen, so dass da alle benutzerspezifischen Preise für den aktuellen Warenkorb rausfallen. Die basketID oben ist jeweils der aktuelle Eintrag in der s_order_basket und identifiziert also einen Eintrag im Warenkorb, etwa “Sonnebrille rot”. 

Besten Gruß,

Daniel

Hi Daniel,

Warum nicht den PriceCalculationService dekorieren? Der wird eigentlich von allen (neuen) Services genutzt soweit ich das bisher verstanden habe. Die sBasket Methoden muss man ja derzeit noch zusätzlich machen, da dort nicht auf diesen Service zugegriffen wird.

Hi,

das hängt vom Anwendungsfall ab, das GraduatedPriceGateway und CheapestPriceGateway könntest du bspw. für ES und SQL unterschiedlich dekorieren - das sind auch die “direktesten” Datenquellen für die Preise. Der PriceCalculationService liegt ja etwas weiter drüber und berücksichtigt auch Mindestabnahme, Discounts etc. Will gar nicht sagen, dass der nicht dafür geeignet ist - aber müsste man eben sicher stellen, dass du jetzt nicht nur den Anzeigepreis überschreibst und im Hintergrund CheapsestPrice und GraduatedPrice-Staffeln noch die alten sind. Kann ja sogar ein Vorteil sein, wenn du von ES und DBAL unabhängig sein willst - dann willst du dich vielleicht gar nicht direkt an die Gateways hängen. Wird also schon passen, denke ich :slight_smile:

Und zur Basket-Methode: Genau, darauf bezog sich mein Beispiel, das überschreibt die Preise im WK. Momentan sind da (wie du schon sagst) immer zwei Sachen, um die man sich kümmern muss: Warenkorb und sonstige Anzeige im Frontend :slight_smile:

Schönen Gruß,

Daniel

Hi,

Danke Daniel, alles klar… Muss mir die PriceGateways mal ansehen. Damit habe ich mich bislang noch nicht beschäftigt. Beim ersten Blick ist mir das aber nicht mehr abstrakt genug.

Ich hoffe halt, dass Ihr den Basket-Service mit Version 5.2.8 wie versprochen fertig habt, dann ist einiges vereinfacht :slight_smile:

@Synonymous schrieb:

Ich hoffe halt, dass Ihr den Basket-Service mit Version 5.2.8 wie versprochen fertig habt, dann ist einiges vereinfacht :)

Na, das habe ich dir aber nicht versprochen :slight_smile: Ist vermutlichein Missverständnis, soweit ich weiß ist für die 5.2.8 (wenn überhaupt) eine Überarbeitung der Payment-Schnittstelle geplant - kann sein, dass das irgendwo als BasketService bezeichnet wurde. Die Überarbeitung des gesamten Warenkorb-Prozesses wird sich vorausssichtlich noch länger ziehen. Ich glaube aber, dass die Core-Jungs da schon früher Einblick gewähren wollen - um Feedback zu sammeln. Mal sehen, das wäre ja sicher auch interessant für dich.

Besten Gruß,

Daniel 

Was nur ein Spass und eine Suggestivunterstellung :slight_smile:
Vor 5.4 rechne ich nicht damit, eher sogar 6.