Commit a23cdbd7 authored by Jonathan Sacksick's avatar Jonathan Sacksick Committed by Jonathan Sacksick
Browse files

Issue #3251240 by jsacksick, TomTech: Commerce cart estimate is causing...

Issue #3251240 by jsacksick, TomTech: Commerce cart estimate is causing orphaned shipments (duplicate shipments in the admin).
parent 4ef8687e
Loading
Loading
Loading
Loading
+18 −1
Original line number Diff line number Diff line
services:
  commerce_cart_estimate.estimator:
    class: Drupal\commerce_cart_estimate\Estimator
    arguments: ['@event_dispatcher', '@commerce_order.order_refresh', '@commerce_shipping.order_manager', '@commerce_shipping.shipment_manager', '@address.subdivision_repository']
    arguments: ['@event_dispatcher', '@commerce_cart_estimate.order_refresh', '@commerce_shipping.order_manager', '@commerce_shipping.shipment_manager', '@address.subdivision_repository']

  logger.channel.commerce_cart_estimate:
    class: Drupal\Core\Logger\LoggerChannel
    factory: logger.factory:get
    arguments: ['commerce_cart_estimate']

  commerce_cart_estimate.order_refresh:
    class: Drupal\commerce_cart_estimate\OrderRefresh
    arguments: ['@commerce_price.chain_price_resolver']
    tags:
      - { name: service_collector, call: addPreprocessor, tag: commerce_order.order_preprocessor }
      - { name: service_collector, call: addProcessor, tag: commerce_order.order_processor }

  commerce_cart_estimate.order_subscriber:
    class: Drupal\commerce_cart_estimate\EventSubscriber\OrderSubscriber
    tags:
      - { name: event_subscriber }

  commerce_cart_estimate.shipment_subscriber:
    class: Drupal\commerce_cart_estimate\EventSubscriber\ShipmentSubscriber
    tags:
      - { name: event_subscriber }

composer.json

0 → 100644
+11 −0
Original line number Diff line number Diff line
{
    "name": "drupal/commerce_cart_estimate",
    "type": "drupal-module",
    "description": "Provides a form for estimating taxes and shipping rates on the cart form.",
    "homepage": "https://drupal.org/project/commerce_cart_estimate",
    "license": "GPL-2.0-or-later",
    "require": {
        "drupal/commerce_shipping": "^2.4"
    },
    "minimum-stability": "dev"
}
+24 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\commerce_cart_estimate;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;

/**
 * Swaps the commerce_shipping late order processor.
 */
class CommerceCartEstimateServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    if ($container->hasDefinition('commerce_shipping.late_order_processor')) {
      $container
        ->getDefinition('commerce_shipping.late_order_processor')
        ->setClass(LateOrderProcessor::class);
    }
  }

}
+33 −21
Original line number Diff line number Diff line
@@ -109,27 +109,30 @@ class Estimator implements EstimatorInterface {
    if (!$this->shippingOrderManager->isShippable($order)) {
      throw new \InvalidArgumentException('The provided order is not shippable.');
    }
    $rated_order = clone $order;
    /** @var \Drupal\commerce_order\Entity\Order $fake_order */
    $fake_order = $order->createDuplicate();
    $fake_order->setData('commerce_cart_estimate', TRUE);
    // Clear the existing shipments reference, to ensure existing shipments
    // are not removed by the shipping order manager.
    $rated_order->set('shipments', NULL);
    $shipments = $this->shippingOrderManager->pack($rated_order, $profile);
    $fake_order->set('shipments', NULL);
    $fake_order->set('order_id', $order->id());
    $fake_shipments = $this->shippingOrderManager->pack($fake_order, $profile);

    // @todo: throw an exception when this happens?
    if (!$shipments) {
    // @todo throw an exception when this happens?
    if (!$fake_shipments) {
      /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
      $address = $profile->get('address')->first();
      throw new CartEstimateException(sprintf('Could not pack the order when estimating shipping. (Country code: %s, Postal code: %s).', $address->getCountryCode(), $address->getPostalCode()));
    }

    $rates = [];
    $rated_order->set('shipments', $shipments);
    $fake_order->set('shipments', $fake_shipments);
    // Rate the shipments returned.
    foreach ($shipments as $shipment) {
    foreach ($fake_shipments as $fake_shipment) {
      // Custom flag to ensure the order isn't repacked during refresh.
      $shipment->setData('owned_by_packer', FALSE);
      $shipment->order_id->entity = $rated_order;
      $rates = $this->shipmentManager->calculateRates($shipment);
      $fake_shipment->setData('owned_by_packer', FALSE);
      $fake_shipment->order_id->entity = $fake_order;
      $rates = $this->shipmentManager->calculateRates($fake_shipment);

      // When no rates could be calculated, throw an exception.
      if (!$rates) {
@@ -141,23 +144,32 @@ class Estimator implements EstimatorInterface {
      $rate_to_apply = reset($rates);
      // Allow customizing the shipping rate that is going to be applied for
      // the cart estimate via an event.
      $event = new SelectShippingRateEvent($rates, $rate_to_apply, $shipment);
      $event = new SelectShippingRateEvent($rates, $rate_to_apply, $fake_shipment);
      $this->eventDispatcher->dispatch(CartEstimateEvents::SELECT_SHIPPING_RATE, $event);
      $this->shipmentManager->applyRate($shipment, $event->getRate());
      $this->shipmentManager->applyRate($fake_shipment, $event->getRate());
    }

    // Refresh the order so that shipping/tax adjustments are applied.
    // Add a custom flag to the order so that custom order processors can target
    // the order refresh performed for estimating shipping.
    $rated_order->setData('commerce_cart_estimate_refresh', TRUE);
    $this->orderRefresh->refresh($rated_order);
    // For some reason, the shipments reference is lost during the refresh, so
    // ensure it's set so it can be used in a preprocess function for the
    // cart estimate summary.
    $rated_order->set('shipments', $shipments);
    $rated_order->recalculateTotalPrice();

    return new CartEstimateResult($rated_order, $rates);
    $fake_order->setData('commerce_cart_estimate_refresh', TRUE);
    $this->orderRefresh->refresh($fake_order);
    $fake_order->recalculateTotalPrice();

    return new CartEstimateResult($fake_order, $rates);
  }

  /**
   * Determine if the order is an estimate.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order.
   *
   * @return bool
   *   Whether the order is an estimate.
   */
  public static function orderIsEstimate(OrderInterface $order): bool {
    return $order->getData('commerce_cart_estimate', FALSE) ?? FALSE;
  }

}
+38 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\commerce_cart_estimate\EventSubscriber;

use Drupal\commerce_cart_estimate\Estimator;
use Drupal\commerce_cart_estimate\Exception\OrderSaveException;
use Drupal\commerce_order\Event\OrderEvent;
use Drupal\commerce_order\Event\OrderEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * The Order Subscriber.
 */
class OrderSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      OrderEvents::ORDER_PRESAVE => ['onPreSave'],
    ];
  }

  /**
   * Ensures an estimated order is not saved.
   *
   * @param \Drupal\commerce_order\Event\OrderEvent $event
   *   The transition event.
   */
  public function onPreSave(OrderEvent $event): void {
    $order = $event->getOrder();
    if ($order !== NULL && Estimator::orderIsEstimate($order)) {
      throw new OrderSaveException('Order estimates cannot be saved');
    }
  }

}
Loading