10

Store products are supplied from different vendors. It is required to create multiple order for every vendor based on its products in the cart during one checkout. Is there any extension to achieve this task or should I start to develop custom checkout module. What about hot points for creating such extension vision of experienced developers of Magento? Can you explain me brief checkout flow architecture Magento friendly (as possible as code level)? Thanks much more!

  • Have a look on the Multishipping which magento offers out of the box. And there is dropship but I have no idea whether it is good or what it is able to do. unirgy.com/products/udropship – Fabian Blechschmidt Jun 12 '13 at 13:52
  • @mageUz, have you worked below answer?. it's not worked for me.. Can post your code? – Manoj Kumar Dec 15 '14 at 11:46
  • @ManojKumar, yes I have already implement order splitting using multiaddress checkout logic, I am sure that the logic given below also works perfectly. – mageUz Dec 15 '14 at 13:48
  • @mageUz, when use this code Shopping Cart is Empty showing.. Any suggestions.. – Manoj Kumar Dec 17 '14 at 13:22
  • Hi that can be done using marketplace split cart module store.webkul.com/Magento-Marketplace-Split-Cart.html . Thanks – webkul Jun 1 '15 at 11:43
9

It is doable quite easily with a rewrite of the checkout/type_onepage model.
In that class override the saveOrder() method as follows:

public function saveOrder()
{
    $quote = $this->getQuote();

    // First build an array with the items split by vendor
    $sortedItems = array();
    foreach ($quote->getAllItems() as $item) {
        $vendor = $item->getProduct()->getVendor(); // <- whatever you need
        if (! isset($sortedItems[$vendor])) {
            $sortedItems[$vendor] = $item;
        }
    }
    foreach ($sortedItems as $vendor => $items) {
        // Empty quote
        foreach ($quote->getAllItems() as $item) {
            $quote->getItemsCollection()->removeItemByKey($item->getId());
        }
        foreach ($items as $item) {
            $quote->addItem($item);
        }
        // Update totals for vendor
        $quote->setTotalsCollectedFlag(false)->collectTotals();

        // Delegate to parent method to place an order for each vendor
        parent::saveOrder();
    }
    return $this;
}

But be aware that in Magento a payment is associated with an invoice, and each invoice is associated with an order.

In consequence this means that as soon as you have multiple orders, you will also have split the payments. So this is only feasible if the payment method doesn't require user interaction during the payment.

UPDATE: The orginal answer delegated to parent::save() which had to be parent:saveOrder(). It is fixed in the example code now.

  • Would appreciate if you could have a look at my similar question! magento.stackexchange.com/questions/6974/… – CaitlinHavener Aug 20 '13 at 21:22
  • were you able to split the order using above code, as i am trying to do the same but not without any luck – Deepak Mallah Feb 26 '14 at 5:41
  • 1
    Roy, of course you have to extend the original class for there to be a parent, but that is simple PHP, it has nothing to do with Magento. The method saveOrder still is present and active in Magento CE 1.9 just like it has always been. – Vinai Jun 7 '14 at 7:10
  • 3
    I had a problem with this snippet as the 2 order have the grandtotal and subtotal equal the entire order. After debugging i found out that even removing and re-adding the items, it would just use address cached items after collecting Total form the address... To solve it just clear for each address the items cache: $address->unsetData('cached_items_all'); $address->unsetData('cached_items_nominal'); $address->unsetData('cached_items_nonnominal'); – Diogo Santiago Oct 22 '15 at 17:25
  • 1
    @Vinai $quote->addItem($item); this code not working. After adding item I echo $quote->getAllItems() with help of foreach loop. But there was no item. Can you help me on this please? – Amit Bera Mar 30 '16 at 5:59
1

The following is tested in CE ver 1.9.0.x

/**
 * Overwrite core
 */
class Vendor_Module_Model_Checkout_Type_Onepage extends Mage_Checkout_Model_Type_Onepage
{
    protected $_oriAddresses = array();

    /**
     * Prepare order from quote_items  
     *
     * @param   array of Mage_Sales_Model_Quote_Item 
     * @return  Mage_Sales_Model_Order
     * @throws  Mage_Checkout_Exception
     */
    protected function _prepareOrder2($quoteItems)
    {
        $quote = $this->getQuote();
        $quote->unsReservedOrderId();
        $quote->reserveOrderId();

        // new instance of quote address
        $quote->setIsMultiShipping(true); // required for new instance of Mage_Sales_Model_Quote_Address
        $address = Mage::getModel('sales/quote_address');
        $weight = 0;
        $addressType = 'billing';
        foreach ($quoteItems as $quoteItem) {
            $address->addItem($quoteItem, $quoteItem->getQty());
            $weight += $quoteItem->getWeight();
            if (!$quoteItem->getIsVirtual()) {
                $addressType = 'shipping';
            }
        }
        // get original shipping address that contains multiple quote_items
        if (!isset($this->_oriAddresses[$addressType])) {
            $this->_oriAddresses[$addressType] = Mage::getResourceModel('sales/quote_address_collection')
                ->setQuoteFilter($quote->getId())
                ->addFieldToFilter('address_type', $addressType)
                ->getFirstItem();
        }
        Mage::helper('core')->copyFieldset('sales_convert_quote_address', 'to_customer_address', $this->_oriAddresses[$addressType], $address);
        Mage::helper('core')->copyFieldset('sales_convert_quote_address', 'to_order', $this->_oriAddresses[$addressType], $address);
        $address->setQuote($quote)
            ->setWeight($weight)
            ->setSubtotal(0)
            ->setBaseSubtotal(0)
            ->setGrandTotal(0)
            ->setBaseGrandTotal(0)
            ->setCollectShippingRates(true)
            ->collectTotals()
            ->collectShippingRates()        
            ;

        $convertQuote = Mage::getSingleton('sales/convert_quote');
        $order = $convertQuote->addressToOrder($address);
        $order->setBillingAddress(
            $convertQuote->addressToOrderAddress($quote->getBillingAddress())
        );

        if ($address->getAddressType() == 'billing') {
            $order->setIsVirtual(1);
        } else {
            $order->setShippingAddress($convertQuote->addressToOrderAddress($address));
        }

        $order->setPayment($convertQuote->paymentToOrderPayment($quote->getPayment()));
        if (Mage::app()->getStore()->roundPrice($address->getGrandTotal()) == 0) {
            $order->getPayment()->setMethod('free');
        }

        foreach ($quoteItems as $quoteItem) {
            $orderItem = $convertQuote->itemToOrderItem($quoteItem);  // use quote_item to transfer is_qty_decimal
            if ($quoteItem->getParentItem()) {
                $orderItem->setParentItem($order->getItemByQuoteItemId($quoteItem->getParentItem()->getId()));
            }
            $order->addItem($orderItem);
        }

        return $order;
    }

    /**
     * Overwrite core function
     */
    public function saveOrder()
    {
        $quote = $this->getQuote();
        if ($quote->getItemsCount() > 1) {
            $items = $quote->getAllVisibleItems();
            $group = array();
            $split = array();
            foreach ($items as $item) {
                if (Mage::helper('vendor')->checkSku($item->getSku())) {
                    $split[] = array($item); // one item per order
                } else {
                    $group[] = $item; // all other items in one order
                }
            }
            if (count($split)) {
                if (count($group)) {
                    $split[] = $group;
                }
                return $this->_splitQuote($split);
            }
        }
        return parent::saveOrder();
    }

    /**
     * Split quote to multiple orders
     * 
     * @param array of Mage_Sales_Model_Quote_Item
     * @return Mage_Checkout_Model_Type_Onepage
     */
    protected function _splitQuote($split)
    {
        $this->validate();
        $isNewCustomer = false;
        switch ($this->getCheckoutMethod()) {
            case self::METHOD_GUEST:
                $this->_prepareGuestQuote();
                break;
            case self::METHOD_REGISTER:
                $this->_prepareNewCustomerQuote();
                $isNewCustomer = true;
                break;
            default:
                $this->_prepareCustomerQuote();
                break;
        }
        if ($isNewCustomer) {
            try {
                $this->_involveNewCustomer();
            } catch (Exception $e) {
                Mage::logException($e);
            }
        }

        $quote = $this->getQuote()->save();
        $orderIds = array();
        Mage::getSingleton('core/session')->unsOrderIds();
        $this->_checkoutSession->clearHelperData();

        /**
         * a flag to set that there will be redirect to third party after confirmation
         * eg: paypal standard ipn
         */
        $redirectUrl = $quote->getPayment()->getOrderPlaceRedirectUrl();

        foreach ($split as $quoteItems) {
            $order = $this->_prepareOrder2($quoteItems);
            $order->place();
            $order->save();
            Mage::dispatchEvent('checkout_type_onepage_save_order_after',
                array('order'=>$order, 'quote'=>$quote));
            /**
             * we only want to send to customer about new order when there is no redirect to third party
             */
            if (!$redirectUrl && $order->getCanSendNewEmailFlag()) {
                $order->sendNewOrderEmail();
            }
            $orderIds[$order->getId()] = $order->getIncrementId();
        }

        Mage::getSingleton('core/session')->setOrderIds($orderIds);

        // add order information to the session
        $this->_checkoutSession
            ->setLastQuoteId($quote->getId())
            ->setLastSuccessQuoteId($quote->getId())
            ->setLastOrderId($order->getId())
            ->setRedirectUrl($redirectUrl)
            ->setLastRealOrderId($order->getIncrementId());

        // as well a billing agreement can be created
        $agreement = $order->getPayment()->getBillingAgreement();
        if ($agreement) {
            $this->_checkoutSession->setLastBillingAgreementId($agreement->getId());
        }

        // add recurring profiles information to the session
        $service = Mage::getModel('sales/service_quote', $quote);
        $profiles = $service->getRecurringPaymentProfiles();
        if ($profiles) {
            $ids = array();
            foreach ($profiles as $profile) {
                $ids[] = $profile->getId();
            }
            $this->_checkoutSession->setLastRecurringProfileIds($ids);
            // TODO: send recurring profile emails
        }

        Mage::dispatchEvent(
            'checkout_submit_all_after',
            array('order' => $order, 'quote' => $quote, 'recurring_profiles' => $profiles)
        );

        return $this;
    }
}

IMPORTANT You need to customize your payment methods to retrieve the grand total from the quote.

  • It is working. Thanks. How to group cart quote by vendor? other then "// one item per order".. – Syed Ibrahim Sep 27 '18 at 14:37
  • 1
    $group[] = $item; // all other items in one order can hold multiple items per order, you can easily modify such that each vendor has its own $group. – kiatng Sep 28 '18 at 2:28
  • Updated and it is working. But Payment is captured only for the last order(If we split multiple order). How we need to update this? I need to collect combined payment total. – Syed Ibrahim Sep 28 '18 at 9:46
  • The total amount is in the quote object, you can load it with $quote->Mage::getModel('sales/quote')->load($lastOrder->getQuoteId()). Then there are many totals you can retrieve from $quote, one of which is $quote->getGrandTotal() – kiatng Oct 1 '18 at 8:46

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