| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 | <?php/** * Refer to LICENSE.txt distributed with the Temando Shipping module for notice of license */namespace Temando\Shipping\Sync;use Magento\Framework\Exception\LocalizedException;use Magento\Framework\Exception\NoSuchEntityException;use Magento\Sales\Api\Data\OrderInterface;use Magento\Sales\Api\Data\ShipmentCreationArgumentsExtensionInterfaceFactory;use Magento\Sales\Api\Data\ShipmentCreationArgumentsInterfaceFactory;use Magento\Sales\Api\Data\ShipmentItemCreationInterface;use Magento\Sales\Api\Data\ShipmentItemCreationInterfaceFactory;use Magento\Sales\Api\Data\ShipmentTrackCreationInterfaceFactory;use Magento\Sales\Api\OrderRepositoryInterface;use Magento\Sales\Api\ShipmentRepositoryInterface as SalesShipmentRepositoryInterface;use Magento\Sales\Api\ShipOrderInterface;use Temando\Shipping\Model\DocumentationInterface;use Temando\Shipping\Model\ResourceModel\Order\OrderReference as OrderReferenceResource;use Temando\Shipping\Model\ResourceModel\Repository\ShipmentReferenceRepositoryInterface;use Temando\Shipping\Model\ResourceModel\Repository\ShipmentRepositoryInterface;use Temando\Shipping\Model\Sales\Service\ShipmentService;use Temando\Shipping\Model\Shipment\PackageInterface;use Temando\Shipping\Model\Shipment\PackageItemInterface;use Temando\Shipping\Model\Shipping\Carrier;use Temando\Shipping\Model\StreamEventInterface;use Temando\Shipping\Sync\Exception\EventException;use Temando\Shipping\Sync\Exception\EventProcessorException;/** * Temando Shipment Event Processor * * @package Temando\Shipping\Sync * @author  Christoph Aßmann <christoph.assmann@netresearch.de> * @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) * @link    https://www.temando.com/ */class ShipmentProcessor implements EntityProcessorInterface{    /**     * @var OrderRepositoryInterface     */    private $salesOrderRepository;    /**     * @var SalesShipmentRepositoryInterface     */    private $salesShipmentRepository;    /**     * @var ShipmentReferenceRepositoryInterface     */    private $shipmentReferenceRepository;    /**     * @var ShipmentRepositoryInterface     */    private $shipmentRepository;    /**     * @var ShipOrderInterface     */    private $shipOrder;    /**     * @var OrderReferenceResource     */    private $orderReferenceResource;    /**     * @var ShipmentItemCreationInterfaceFactory     */    private $itemCreationFactory;    /**     * @var ShipmentTrackCreationInterfaceFactory     */    private $trackCreationFactory;    /**     * @var ShipmentService     */    private $shipmentService;    /**     * @var ShipmentCreationArgumentsInterfaceFactory     */    private $shipmentCreationArgumentsFactory;    /**     * @var ShipmentCreationArgumentsExtensionInterfaceFactory     */    private $shipmentCreationArgumentsExtensionFactory;    /**     * ShipmentEventProcessor constructor.     * @param OrderRepositoryInterface $salesOrderRepository     * @param SalesShipmentRepositoryInterface $salesShipmentRepository     * @param ShipmentReferenceRepositoryInterface $shipmentReferenceRepository     * @param ShipmentRepositoryInterface $shipmentRepository     * @param ShipOrderInterface $shipOrder     * @param OrderReferenceResource $orderReferenceResource     * @param ShipmentItemCreationInterfaceFactory $itemCreationFactory     * @param ShipmentTrackCreationInterfaceFactory $trackCreationFactory     * @param ShipmentService $shipmentService     * @param ShipmentCreationArgumentsInterfaceFactory $shipmentCreationArgumentsFactory     * @param ShipmentCreationArgumentsExtensionInterfaceFactory $shipmentCreationArgumentsExtensionFactory     */    public function __construct(        OrderRepositoryInterface $salesOrderRepository,        SalesShipmentRepositoryInterface $salesShipmentRepository,        ShipmentReferenceRepositoryInterface $shipmentReferenceRepository,        ShipmentRepositoryInterface $shipmentRepository,        ShipOrderInterface $shipOrder,        OrderReferenceResource $orderReferenceResource,        ShipmentItemCreationInterfaceFactory $itemCreationFactory,        ShipmentTrackCreationInterfaceFactory $trackCreationFactory,        ShipmentService $shipmentService,        ShipmentCreationArgumentsInterfaceFactory $shipmentCreationArgumentsFactory,        ShipmentCreationArgumentsExtensionInterfaceFactory $shipmentCreationArgumentsExtensionFactory    ) {        $this->salesOrderRepository = $salesOrderRepository;        $this->salesShipmentRepository = $salesShipmentRepository;        $this->shipmentReferenceRepository = $shipmentReferenceRepository;        $this->shipmentRepository = $shipmentRepository;        $this->shipOrder = $shipOrder;        $this->orderReferenceResource = $orderReferenceResource;        $this->itemCreationFactory = $itemCreationFactory;        $this->trackCreationFactory = $trackCreationFactory;        $this->shipmentService = $shipmentService;        $this->shipmentCreationArgumentsFactory = $shipmentCreationArgumentsFactory;        $this->shipmentCreationArgumentsExtensionFactory = $shipmentCreationArgumentsExtensionFactory;    }    /**     * @param OrderInterface $salesOrder     * @param string $sku     * @return int|null     */    private function getOrderItemIdBySku(OrderInterface $salesOrder, $sku)    {        foreach ($salesOrder->getItems() as $item) {            if ($item->getSku() === $sku) {                return $item->getItemId();            }        }        return null;    }    /**     * Create new shipment     *     * @param string $extShipmentId     * @return int Processed entity ID.     * @throws EventException     * @throws EventProcessorException     */    private function create(string $extShipmentId): int    {        try {            // load external shipment            $shipment = $this->shipmentRepository->getById($extShipmentId);        } catch (LocalizedException $e) {            throw EventProcessorException::processingFailed(                StreamEventInterface::ENTITY_TYPE_SHIPMENT,                $extShipmentId,                $e            );        }        // skip shipment event if no fulfillment with tracking number is available        $fulfillment = $shipment->getFulfillment();        if (!$fulfillment || !$fulfillment->getTrackingReference()) {            throw EventException::operationSkipped(                StreamEventInterface::ENTITY_TYPE_SHIPMENT,                StreamEventInterface::EVENT_TYPE_CREATE,                $extShipmentId,                'No fulfillment information available.'            );        }        // find local order id for external order        $orderId = $this->orderReferenceResource->getOrderIdByExtOrderId($shipment->getOrderId());        if (!$orderId) {            throw EventProcessorException::processingFailed(                StreamEventInterface::ENTITY_TYPE_SHIPMENT,                $extShipmentId            );        }        // items        /** @var ShipmentItemCreationInterface[] $creationItems */        $creationItems = [];        $packages = $shipment->getPackages() ?: [];        /** @var PackageItemInterface[] $fulfilledItems */        $fulfilledItems = array_reduce($packages, function (array $items, PackageInterface $package) {            $items = array_merge($items, $package->getItems());            return $items;        }, []);        $salesOrder = $this->salesOrderRepository->get($orderId);        foreach ($fulfilledItems as $fulfilledItem) {            if (!isset($creationItems[$fulfilledItem->getSku()])) {                // add new creation item                $orderItemId = $this->getOrderItemIdBySku($salesOrder, $fulfilledItem->getSku());                $creationItem = $this->itemCreationFactory->create();                $creationItem->setQty($fulfilledItem->getQty());                $creationItem->setOrderItemId($orderItemId);                $creationItems[$fulfilledItem->getSku()] = $creationItem;            } else {                // increase qty of existing creation item                $creationItem = $creationItems[$fulfilledItem->getSku()];                $creationItem->setQty($fulfilledItem->getQty() + $creationItem->getQty());            }        }        // tracking        $tracking = $this->trackCreationFactory->create();        $tracking->setCarrierCode(Carrier::CODE);        $tracking->setTitle($fulfillment->getServiceName());        $tracking->setTrackNumber($fulfillment->getTrackingReference());        // shipping label        /** @var DocumentationInterface $documentation */        $documentation = current($shipment->getDocumentation());        $labelUrl = empty($documentation) ? '' : $documentation->getUrl();        $extensionAttributes = $this->shipmentCreationArgumentsExtensionFactory->create();        $extensionAttributes->setExtLocationId($shipment->getOriginId());        $extensionAttributes->setExtShipmentId($extShipmentId);        $extensionAttributes->setExtTrackingReference($fulfillment->getTrackingReference());        $extensionAttributes->setShippingLabel($labelUrl);        $arguments = $this->shipmentCreationArgumentsFactory->create();        $arguments->setExtensionAttributes($extensionAttributes);        try {            $shipmentId = $this->shipOrder->execute(                $orderId,                $creationItems, // items within partial shipments                true, // Notify the customer (tracking email)                false, // add comment                null,                [$tracking],                [], // Package definition                $arguments            );        } catch (LocalizedException $e) {            throw EventProcessorException::processingFailed(                StreamEventInterface::ENTITY_TYPE_SHIPMENT,                $extShipmentId,                $e            );        }        return (int) $shipmentId;    }    /**     * Update existing shipment, i.e. add tracking information, update status     *     * @param string $extShipmentId     * @return int     * @throws EventException     * @throws EventProcessorException     */    private function modify(string $extShipmentId): int    {        try {            $shipmentReference = $this->shipmentReferenceRepository->getByExtShipmentId($extShipmentId);        } catch (NoSuchEntityException $e) {            return $this->create($extShipmentId);        }        try {            // read shipment from platform, trigger post-process operations            $this->shipmentService->read($extShipmentId, (int) $shipmentReference->getShipmentId());        } catch (NoSuchEntityException $exception) {            // shipment does not exist at platform, skip            throw EventException::operationSkipped(                StreamEventInterface::ENTITY_TYPE_SHIPMENT,                StreamEventInterface::EVENT_TYPE_MODIFY,                $extShipmentId,                $exception->getMessage()            );        } catch (LocalizedException $exception) {            // processing local shipment failed, try again later            throw EventProcessorException::processingFailed(                StreamEventInterface::ENTITY_TYPE_SHIPMENT,                $extShipmentId,                $exception            );        }        return (int) $shipmentReference->getShipmentId();    }    /**     * @param string $operation     * @param string $extShipmentId     * @return int Processed entity ID.     * @throws EventException     * @throws EventProcessorException     */    public function execute(string $operation, string $extShipmentId): int    {        if ($operation == StreamEventInterface::EVENT_TYPE_MODIFY) {            return $this->modify($extShipmentId);        }        if ($operation == StreamEventInterface::EVENT_TYPE_CREATE) {            return $this->create($extShipmentId);        }        throw EventException::unknownOperation(            StreamEventInterface::ENTITY_TYPE_SHIPMENT,            $operation        );    }}
 |