Dynamische Versandkosten auf der Produkt-Detail-Seite anzeigen lassen

Hey Leute,
ich stehe aktuell auf dem Schlauch und komme nicht weiter.
Ich möchte auf der Detailseite die Versandkosten anzeigen lassen, die je nach nachgefragter Menge schwanken können.

Bisher sieht mein Controller so aus:

public function exampleAction(Request $request, SalesChannelContext $context): JsonResponse
    {
        $orderNumber = $request->query->get('orderNumber');
        $quantity = $request->query->get('quantity');
        $productRepository = $this->container->get('product.repository');
        $criteria = new Criteria();
        $criteria->addFilter(new EqualsFilter('productNumber', $orderNumber));
        $product = $productRepository->search($criteria, Context::createDefaultContext())->first();
        $productId = $product->getUniqueIdentifier();
        
        $fakeCart = new Cart(self::DUMMY_TOKEN,$context);

        $lineItem = new LineItem("test", LineItem::PRODUCT_LINE_ITEM_TYPE,$productId,1);
        $lineItem->setLabel("TEST");
        $lineItem->setGood(false);
        $lineItem->setStackable(true);
        $lineItem->setRemovable(true);
        $lineItem->setQuantity((int) $quantity);
        $fakeCart->add($lineItem);
        
        $behavior = new CartBehavior([], true, true);
        $fakeCart = $this->cartRuleLoader->loadByCart($context, $fakeCart, $behavior)->getCart();
        
        $shippingCost = $fakeCart->getShippingCosts();



        return new JsonResponse(['shipping_cost' => $shippingCost]);
    }

Bisher habe ich versucht ein zweiten „Fake“-Cart zu nutzen, damit ich wirklich nur die Versandkosten für eine Produkt bekomme. (Sollten andere Produkte im echten Cart sein, werden die Versandkosten berechnet → deswegen der Fake-Cart).

Eine Fehlermeldung war:

Shopware\Core\Checkout\Cart\Delivery\DeliveryCalculator::getCurrencyPrice(): Argument #1 ($priceCollection) must be of type Shopware\Core\Framework\DataAbstractionLayer\Pricing\PriceCollection, null given

Wie könnte man hier vorgehen bzw. vielleicht habt ihr eine andere Idee. :slight_smile:

ich schmeiss Dir einfach mal ein Stück Code vor die Füße. Ich denke damit kommst Du sicher besser zu Recht:

public function cartDebugger(Request $request, SalesChannelContext $salesChannelContext, CartService $cartService): Response
{
    $env = getenv("APP_ENV");
    if ($env !== 'dev') {
        // throw new Exception("NOT_ALLOWED");
    }
    
    $cart = $cartService->getCart($salesChannelContext->getToken(), $salesChannelContext);
    dump($cart);
    dump($_SESSION);
    die();
    return new Response("debug");
}

bei Fragen einfach fragen …

noch was: bei Deinem alten Code am besten die DI Objekte einfach als Parameter der Controller Methode hinzu fügen, die Controller sind „autowired“, normalerweise.

Und product.repository ist zwar auf den ersten Blick richtig, aber dann doch wieder nicht, viel besser ist hier „sales_channel.product.repository“ dann stimmen auch die Preise !!!

Danke für deine Antwort. Mit deinem Code kann ich den Cart debuggen. Ich habe jetzt sales_channel.product.repository genutzt.

Allerdings weiß ich nicht, wo der Fehler liegt und wieso $priceCollection nicht übergeben wird. Das klappt ja beim „normalen“ Cart auch :smiley:

  public function exampleAction(Request $request, SalesChannelContext $context): JsonResponse
    {

        $orderNumber = $request->query->get('orderNumber');
        $quantity = $request->query->get('quantity');
        $productRepository = $this->container->get('product.repository');
        $criteria = new Criteria();
        $criteria->addFilter(new EqualsFilter('productNumber', $orderNumber));
        $product = $productRepository->search($criteria, Context::createDefaultContext())->first();
        $productId = $product->getUniqueIdentifier();

        $cart = $this->cartService->getCart($context->getToken(), $context);
        $cart->setToken(self::DUMMY_TOKEN);

        $productLineItem = new LineItem($productId, LineItem::PRODUCT_LINE_ITEM_TYPE, $productId);
        $productLineItem->setLabel("test");
        $productLineItem->setGood(true);
        $productLineItem->setStackable(true);
        $productLineItem->setRemovable(true);
        $productLineItem->setQuantity((int) $quantity);


        $cart->add($productLineItem);



        $behavior = new CartBehavior([], true, true);
        $cart = $this->cartRuleLoader->loadByCart($context, $cart, $behavior)->getCart();

        $shippingCost = $cart->getShippingCosts()->getTotalPrice();

        return new JsonResponse(['shipping_cost'=>$shippingCost]);
    }

Mit diesem Code klappt es, allerdings werden nun auch die Posten im Warenkorb mit kalkuliert.

sorry ich hatte die Antwort übersehen. Ich frage mich gerade wo denn Sinn des Ganze ist ? Kurz zur Erklärung wie Shopware mit dem Warenkorb umgeht.
Letztlich der Warenkorb nichts anderes als eine Liste von LineItems. Das können dann Product sein, Rabatte und Zuschläge, Versandkosten, Steuern. Aber auch irgendwelche Elemente, die intern benötigt werden.

Sobald irgendwas „gemacht“ wird, z.B. neuen Artikel dazu oder Anzahl geändert, durch der Warenkorb einen „Re-Calculation“. Dabei werden dann die alle sog. CartProcessor Objekte aufgerufen, die dann die Berechnung durchführen können. Dabei kann die Reihenfolge definiert werden. Wichtig ist hier, dass der gesamte Warenkorb übergeben wird.

Im Standard bringt dann ein paar CartProcessors mit. Zunächst werden die Preise der Zeilen berechnet ala Anzahl mal Einzelpreis gleich Gesamt. Wenn Staffelpreise gibt dann entsprechend wie definiert.
Danach werden Rabatte berechnet, die sich auf den gesamten Warenkorb beziehen. Und irgendwann dann auch die Versandkosten. Ganz am Ende wird der Warenkorb dann in die DB serialisiert.

Wenn Du jetzt den Warenkorb (wie hier in dem Debugger) lädst, dann ist dieser schon komplett durchgerechnet.

Nun die Frage, die ja gelöst werden soll. Die Versandkosten beziehen sich ja auf den gesamten Warenkorb und nicht nur auf einen einzelnen Artikel, oder nicht ?

Auf jeden Fall müsste das Verfahren dann eher so aussehen:

  1. Man erzeugt einen neuen „leeren“ Warenkorb
  2. Diesem werden die Artikel hinzugefügt
  3. Jetzt läuft die Neuberechnung und im Ergebnis dann auch die Versandkosten. Das muss aber „angestoßen“ werden.
  4. Die Versandkosten werden „abgegriffen“
  5. Der temporäre Warenkorb wird wieder gelöscht

„Richtig richtig“ wäre dann in 1) nicht mit einem leeren Warenkorb zu beginnen, sondern zunächst den aktuellen Warenkorb einzukopieren. Alternativ könnte man für den Warenkorb eine Version erzeugen und am Ende zu verwerfen.

Das Verfahren funktioniert zwar ist aber weder richtig dokumentiert noch simpel; ich habe eine ähnliche Geschichte mit knapp 2000 Zeilen.

However nur so funktioniert die Berechnung wenn sich die Preis-Berechnung ändert, irgendwelche Plugin z.B. in die Versandkosten-Berechnungen eingreifen, Rabatte greifen, etc.

Und als „interim“ ginge auch
$this->cartService->createNew("1234");

anstatt getCart

Hey, ich bin es wieder. Mein Code hat funktioniert. Jetzt existieren aber leider mehrere Versandregeln, die meinen Controller aushebeln. Wechselt die Versandart von z.B. Paket zu Spedition, dann werden die Versandkosten nicht mehr richtig angezeigt. Gibt es keine andere Art, das zu lösen (bis auf die 2000 Zeilen-Lösung?) :smiley:

  /**
     * @Route("/shipping-cost", name="frontend.shipping-cost", methods={"GET"}, defaults={"XmlHttpRequest"=true})
     */
    public function exampleAction(Request $request, SalesChannelContext $context): JsonResponse
    {
        $orderNumber = $request->query->get('orderNumber');
        $quantity = (int) $request->query->get('quantity');
        $productRepository = $this->container->get('product.repository');

        $productId = $this->findProductIdByOrderNumber($productRepository, $orderNumber);

        $cart = $this->cartService->createNew("1234")

        $productLineItem = $this->createProductLineItem($productId, $quantity);
        $cart->add($productLineItem);
        $cart = $this->cartRuleLoader->loadByCart($context, $cart, new CartBehavior([], true, true))->getCart();

        $shippingCost = $cart->getShippingCosts()->getTotalPrice();

        return new JsonResponse(['shipping_cost' => $shippingCost]);
    }

Die Frage wäre ob die Regeln tatsächlich greifen. Du arbeitest ja wohl mit einem „leeren“ und „keinem Kunden“ zugeordneten Warenkorb. (mal abgesehen von dem einem Artikel). Ist denn die Preis-Berechnung (bwz. Versandkosten-Berechnung) korrekt wenn man den Artikel wirklich in den Warenkorb legt ?

Dann würde ich die Logik so aufbauen, dass hier zunächst steht: "Versandkosten ab: " und dann macht man nur einen Call mit der exakten Rechnung auf Benutzer-Interaktion (sprich ein Button/Link).