1

We want a feature to allow each item in cart to be shipped to multiple addresses in a single order.

Magento does provide multishipping option, but it is applicable to all items in the cart in the same order.

We want to provide an option to customer to add multiple addresses against each item in cart (So that it can be sent as a gift to multiple addresses at once in the same order)

For e.g. if there are 2 items in the cart Item A and Item B, then Item A and Item B should be able to ship to different set of addresses.

Can anyone suggest how this can be achieved or if there are any extensions which can provide the above feature?

1

This is not readily available in Magento 2. Neither there are any 3rd party extensions. We had similar requirement from one of our client. Initially, we started with Magento but had to switch to other technology later on due to time constraints.

You can use below sites for reference flow available in other frameworks

https://mrbchocolates.com/collections/chocolate-bars

https://multiship.myshopify.com/collections/all

Magento's inbuilt multishipping option provides to add one address per item. It creates a new order for each shipping address. You will have to rewrite entire multishipping module. Especially to merge them into a single order is an uphill task. Besides not all payment methods support multiple addresses.

However you can follow below steps to begin with

Override entire multishipping module vendor/magento/module-multishipping/ to app/code/Vendor/Module/ and customize it as per your needs

enter image description here

In file vendor/magento/module-multishipping/Model/Checkout/Type/Multishipping.php

Below function adds address to the quote item. You can manipulate $data parameter to accept multiple addresses via post request and make changes accordingly.

protected function _addShippingItem($quoteItemId, $data)
{
    //sample logic
    $quoteItem = $quote->getItemById($quoteItemId);
    if($quoteItem) {
        foreach($data['addresses'] as $address) {
            $quoteAddress = $this->_addressFactory->create()->importCustomerAddressData($address);
            $quote->addShippingAddress($quoteAddress);
            $quoteAddress->addItem($quoteItem, $qty);
        }
    }
}

The quote item to address mappings are stored in the table 'quote_address_item'.

app/code/Vendor/Module/Model/Checkout/Type/Multishipping.php

/**
 * Get quote addresses assigned to different quote items.
 *
 * Based on result array we can display each address separately
 *
 * @param int $itemId
 * @return array
 */
public function getQuoteShippingAddressItemsByQuoteItemId($itemId)
{
    $addressItems = [];
    $addresses = $this->getQuote()->getAllShippingAddresses();
    foreach ($addresses as $address) {
        foreach ($address->getAllItems() as $item) {
            if ($item->getParentItemId()) {
                continue;
            }
            if ($item->getProduct()->getIsVirtual()) {
                continue;
            }
            if($item->getQuoteItemId() == $itemId){
                $addressItems[] = $item;
            }
        }
    }

    return $addressItems;
}

app/code/Vendor/Module/view/frontend/templates/checkout/addresses.phtml

<?php foreach ($block->getItems() as $_index => $_item) : ?>
    <?php if ($_item->getQuoteItem()) : ?>
        <tr>
            <!-- other code -->
            <!-- other code -->
            <!-- other code -->
            <td class="col address" data-th="<?= $block->escapeHtmlAttr(__('Send To')) ?>">
                <?php if ($_item->getProduct()->getIsVirtual()) : ?>
                    <div class="applicable">
                        <?= $block->escapeHtml(__('A shipping selection is not applicable.')) ?>
                    </div>
                <?php else : ?>
                    <div class="field address">
                        <label for="ship_<?= $block->escapeHtml($_index) ?>_<?= $block->escapeHtml($_item->getQuoteItemId()) ?>_address"
                               class="label">
                            <span><?= $block->escapeHtml(__('Send To')) ?></span>
                        </label>
                        <div class="control">
                            <?= $block->getAddressesHtmlSelect($_item, $_index) ?>
                        </div>
                    </div>
                <?php endif; ?>
            </td>
            <!-- other code -->
            <!-- other code -->
            <!-- other code -->
        </tr>
    <?php endif; ?>
<?php endforeach; ?>

app/code/Vendor/Module/Block/Checkout/Addresses.php

/**
 * @return array
 */
public function getItems()
{
    $items = $this->getCheckout()->getQuote()->getAllVisibleItems();
    return $items;
}

/**
 * Get all shipping addresses by Item.
 *
 * @param int $itemId
 * @return array
 */
public function getAllShippingAddressesByItem($itemId)
{
    return $this->getCheckout()->getQuoteShippingAddressItemsByQuoteItemId($itemId);
}

/**
 * Retrieve HTML for addresses dropdown
 *
 * @param mixed $item
 * @param int $index
 * @return string
 */
public function getAddressesHtmlSelect($itemId)
{
    $addressItems = $this->getAllShippingAddressesByItem($itemId);
    $index = 1;
    $selectHtml = '';
    $selectHtml .= '<div class="address-select">';

    if(empty($addressItems)) {
        $select = $this->getLayout()->createBlock(\Magento\Framework\View\Element\Html\Select::class)
            ->setName('ship[' . $itemId . '][address][' . $index . ']')
            ->setId('ship_' . $itemId . '_address_'. $index)
            ->setValue('')
            ->setOptions($this->getAddressOptions());
        $selectHtml = $select->getHtml();
    } else {
        foreach ($addressItems as $addressItem) {
           $select = $this->getLayout()->createBlock(\Magento\Framework\View\Element\Html\Select::class)
                ->setName('ship[' . $itemId . '][address][' . $index . ']')
                ->setId('ship_' . $itemId . '_address_'. $index)
                ->setValue($item->getCustomerAddressId())
                ->setOptions($this->getAddressOptions());
            $selectHtml .= $select->getHtml();
            $index++;
        }
    }
    $selectHtml .= '</div>';

    return $selectHtml;
}

/**
 * Retrieve options for addresses dropdown
 *
 * @return array
 */
public function getAddressOptions()
{
    $options = [];
    $addresses = [];

    $options[] = [
            'value' => '',
            'label' => 'Select an Address',
        ];

    try {
        $addresses = $this->customerRepository->getById($this->getCustomerId())->getAddresses();
    } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
        /** Customer does not exist */
    }
    /** @var \Magento\Customer\Api\Data\AddressInterface $address */
    foreach ($addresses as $address) {
        $label = $this->_addressConfig
            ->getFormatByCode(AddressConfig::DEFAULT_ADDRESS_FORMAT)
            ->getRenderer()
            ->renderArray($this->addressMapper->toFlatArray($address));

        $options[] = [
            'value' => $address->getId(),
            'label' => $label,
        ];
    }

    return $options;
}

Hope this helps to some extent.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.