Magento - Quote/order product item attribute based on user input

41,849

Solution 1

Magento provides a capability for adding options that aren't product attributes or product custom options. They are set on the product and quote items with the option code additional_options.

There are two steps you need to take, each can be handled via an event observer. If you want the additional options to carry through reordering, you will need also observe a third event.

Add options to quote item

The first step is to add the event observer to set the additional options on the loaded product before it is added to the cart. One option is to use the catalog_product_load_after event.

<catalog_product_load_after>
    <observers>
        <extra_options>
            <type>model</type>
            <class>extra_options/observer</class>
            <method>catalogProductLoadAfter</method>
        </extra_options>
    </observers>
</catalog_product_load_after>

In the event observer you can add additional checks the requested page is indeed an add to cart action. The main point of this observer method is to add the selection of your special options to the additional_options option on the product model.

public function catalogProductLoadAfter(Varien_Event_Observer $observer)
{
    // set the additional options on the product
    $action = Mage::app()->getFrontController()->getAction();
    if ($action->getFullActionName() == 'checkout_cart_add')
    {
        // assuming you are posting your custom form values in an array called extra_options...
        if ($options = $action->getRequest()->getParam('extra_options'))
        {
            $product = $observer->getProduct();

            // add to the additional options array
            $additionalOptions = array();
            if ($additionalOption = $product->getCustomOption('additional_options'))
            {
                $additionalOptions = (array) unserialize($additionalOption->getValue());
            }
            foreach ($options as $key => $value)
            {
                $additionalOptions[] = array(
                    'label' => $key,
                    'value' => $value,
                );
            }
            // add the additional options array with the option code additional_options
            $observer->getProduct()
                ->addCustomOption('additional_options', serialize($additionalOptions));
        }
    }
}

The additional options will be moved from the product to the quote item automatically. With this observer in place, your options will appear in the cart and the checkout review.

Add options to order item

In order to have them persist, one additional observer is needed (only since Magento 1.5).

<sales_convert_quote_item_to_order_item>
    <observers>
        <extra_options>
            <type>model</type>
            <class>extra_options/observer</class>
            <method>salesConvertQuoteItemToOrderItem</method>
        </extra_options>
    </observers>
</sales_convert_quote_item_to_order_item>

Here we move the option from the quote item to the order item.

public function salesConvertQuoteItemToOrderItem(Varien_Event_Observer $observer)
{
    $quoteItem = $observer->getItem();
    if ($additionalOptions = $quoteItem->getOptionByCode('additional_options')) {
        $orderItem = $observer->getOrderItem();
        $options = $orderItem->getProductOptions();
        $options['additional_options'] = unserialize($additionalOptions->getValue());
        $orderItem->setProductOptions($options);
    }
}

From this point on the additional options will be visible in the customer order history in the frontend and the order emails, as well as in the admin interface order view, invoices, shipments, creditmemos and PDFs.

Add support for reorders

In order to carry the oprions over to the new order during a reorder, you need to take care to copy them over. Here is one possibility using the checkout_cart_product_add_after event.

<checkout_cart_product_add_after>
    <observers>
        <extra_options>
            <type>singleton</type>
            <class>extra_options/observer</class>
            <method>checkoutCartProductAddAfter</method>
        </extra_options>
    </observers>
</checkout_cart_product_add_after>

The parsing of the extra options and building the additional options array should be moved into a separate function to avoid code duplication, but for this example I'll leave the required logic for each method in place for clarity.

public function checkoutCartProductAddAfter(Varien_Event_Observer $observer)
{
    $action = Mage::app()->getFrontController()->getAction();
    if ($action->getFullActionName() == 'sales_order_reorder')
    {
        $item = $observer->getQuoteItem();
        $buyInfo = $item->getBuyRequest();
        if ($options = $buyInfo->getExtraOptions())
        {
            $additionalOptions = array();
            if ($additionalOption = $item->getOptionByCode('additional_options'))
            {
                $additionalOptions = (array) unserialize($additionalOption->getValue());
            }
            foreach ($options as $key => $value)
            {
                $additionalOptions[] = array(
                    'label' => $key,
                    'value' => $value,
                );
            }
            $item->addOption(array(
                'code' => 'additional_options',
                'value' => serialize($additionalOptions)
            ));
        }
    }
}

Translation:

There is no mechanism in place to translate these option labels or values. Here are a few ideas that might be useful in that regard.

In a quote_item_load_after event observer, get the additional options array and set $option['print_value'] = $helper->__($option['value']);. If print_value is set, Magento will use that for rendering the display.
The same can be done with order items.

There is no such thing as a print_label, but you could set a custom index (label_source maybe) and set the label on the fly using that as the source, e.g. $option['label'] = $helper->__($option['label_source']);.

Beyond that you would probably have to resort to modifying the templates (grep for getItemOptions()), or overriding the block classes (grep additional_options).

Solution 2

It is possible to add custom fields to the Quote item. How to add custom fields for Order Line Items in Magento to get started. I've used these instruction recently to add a custom fields to a Magento Quote Item and the concept is fine, but there are a few practices in that article that are not great. Things I'd do differently:

  1. Use a setup script to add fields to the database rather than doing it directly.
  2. Use Magento's Request object rather than accessing $_REQUEST directly.
  3. Use extensions and rewrites rather than modifying the Magento core.
  4. Make the changes to config.xml from an extension rather than modifying the core.

Generally it's best to avoid modifying the Magento core, and apply your customisations via a module as it makes upgrades easier/possible in the future. If you've not created your own extension before moduleCreator can help you generate the necessary boilerplate.

Solution 3

My Solution in Magento 1.8

Set option to quote item

$quoteItem = $cart->getQuote()->getItemById($itemId);
$quoteItem->addOption(array('label' => 'buymode', 'code' => 'buymode', 'value' => $data['buymode']));
$quoteItem->save();

Access option from QuoteItem

$quoteItem->getOptionByCode('buymode')->getValue();

Transfer option to OrderItem

Register for event sales_convert_quote_item_to_order_item

public function onConvertQuoteItemToOrderItem($observer) {
    $orderItem = $observer->getOrderItem();
    $quoteItem = $observer->getItem();
    $options = $orderItem->getProductOptions();
    $options['buymode'] = $quoteItem->getOptionByCode('buymode')->getValue();
    $orderItem->setProductOptions($options);
}

Access option from OrderItem

$orderItem->getProductOptionByCode('buymode')
Share:
41,849
Vitamin
Author by

Vitamin

Updated on July 05, 2022

Comments

  • Vitamin
    Vitamin almost 2 years

    Summary

    I want to create a product attribute that is not saved to products, or displayed on the product edit page like ordinary product attributes. Instead I want it to be saved to order/quote items and displayed on orders, invoices and such. It should also be configurable by the customer in the frontend before adding a product to the cart.

    Details

    • Just like with Custom Options, a form element should be added to the frontend product page.
      • Unlike Custom Options, this is not an actual product attribute. It should not be displayed on the admin product pages or attribute sets.
      • The customer is required to provide a valid value. I need to be able to do server-side validation.
      • I want to have a .phtml template generating its html. Currently I'm able to override app/design/frontend/base/default/catalog/product/view/type/default.phtml with satisfactory (design) results. However I don't know how to capture, validate and eventually save its value.
    • The value of this form element should be saved with the quote/order product item.
      • This value should be displayed on any and all invoices, orders, sales emails.
      • I want to control output with a template, or at least be able to return the string that is used to display the value

    My questions

    1. How do I validate and eventually save the value from a <input> on the frontend product page to the quote item when the product is added to the cart, and later in the checkout process to the order item?
    2. How do I display this value on the order, invoice, sales emails and such pages?
    3. How do I filter an order collection to fetch orders that has items with my value set to a specific value?

    Update 1

    I've discovered that I can run this code on a catalog/product model (and probably sales/quote_item as well) during events such as sales_quote_item_qty_set_after

    $infoBuyRequest = $product->getCustomOption('info_buyRequest');
    $buyRequest = new Varien_Object(unserialize($infoBuyRequest->getValue()));
    $myData = $buyRequest->getMyData();
    

    In this way I was able to retrieve my custom, customer supplied, data from my <input> on the product page.

    I suspect this info_buyRequest is saved with the quote and order items. If so, this partially solved my problems 1 and 2. However, I still dont know where it's suitable to run this code, and I dont know how to display it on the backend order/quote/report pages. Also I belive since this is stored as a serialized value in the database, it will be most difficult to get quote/order item collections based on my custom data.

  • Vitamin
    Vitamin about 12 years
    So are additional_options treated in a certain way by Magento, and if so, don't I run the risk of incompatibility with other modules if I overwrite it? I have not yet tried your approach, but I'll ask this anyway: will the values in additional_options automagically be displayed throughout the Magento backend on orders and sales emails?
  • Vitamin
    Vitamin about 12 years
    I forgot to thank you for your insightful answer and effort. Thanks! :)
  • Vinai
    Vinai about 12 years
    That is correct, additional_options are treated in a certain way by Magento. I updated the example code to get the additional options from the product or item if they already have been set by a different module. This way there will be no conflicts because this module overwrites additional_options set by another extension. If you go this way, you might also want to check if your additional option already exists on the array, just to be safe. For this purpose you can specify an additional value besides 'label' and 'value' for it, e.g. 'code'. But there is always more error checking to add :)
  • Vitamin
    Vitamin about 12 years
    That's great. I have only one more requirement that I suspect is not easily done with this approach. I need label to be translated, and I want to filter value before it is displayed, so that I may change the value that is displayed according to locale or any other reason I might come up with.
  • Vinai
    Vinai about 12 years
    Added a section regarding the translation of additional_options.
  • Vitamin
    Vitamin about 12 years
    Brilliant! I do not have the time to test this out right now, but I pretty much consider my question answered by this. I had a point about filtering an order collection by these values, but I'm almost certain that is not possible using SQL. Looping through the orders and their products in search of certain values in additional_options should suffice.
  • Vinai
    Vinai about 12 years
    Since the options are stored as serialized PHP I agree filtering via SQL would be a mess. In that case adding an order item attribute(s) and setting it/them to the value(s} of your custom option would probably be a better.
  • Tom
    Tom about 12 years
    Is there a class that we will be extending with this in our extensions or some sort of "config.phtml" file that we use in our extension? Where would this code go?
  • Bryan Ruiz
    Bryan Ruiz about 12 years
    looks like this won't work with the simple products in a grouped product.. unless im doing something wrong.. it seems the grouped product is the one caught by the event yet the simple product is the one that is turned into a quote item. Very cool snippet though.
  • Bryan Ruiz
    Bryan Ruiz about 12 years
    ok so for a grouped product, you want to get the product from the item from this event: checkout_cart_product_add_after and then check that it is simple or whatever you expect it to be using getTypeId() and then follow the same method above using addOption to the item instead of the product
  • molleman
    molleman over 11 years
    Hello i compute my custom value within the observer from 2 custom options fields, i see you are getting the options from the request, how would i go about building my options array to store the value of one additional option called distance?
  • Vinai
    Vinai over 11 years
    Getting the values from the request is just an example based on the original question posted. It doesn't matter at all where you get the values for the additional_options option from. To get a custom product option value use $quoteItem->getOptionByCode('option_<N>')->getValue(), where N is the numeric ID of that option.
  • phagento
    phagento over 10 years
    @Vinai the code you gave works on my local but when I deploy it to the live server, the event listeners are not fired. Do you know why?
  • ROBIN
    ROBIN about 10 years
    @vinai custom options are not working at reorder. $item->addOption(array( 'product'=>$item->getProduct(),'code' => 'additional_options', 'value' => serialize($additionalOptions) )); I've fixed that by above. I am using 1.8
  • Alex Lacayo
    Alex Lacayo over 9 years
    Excellent! Helped me out tremendously!
  • user2737980
    user2737980 almost 9 years
    How to display such custom field in product page? magento.stackexchange.com/questions/73026/…
  • Shawn Abramson
    Shawn Abramson almost 9 years
    I had been trying to get my dynamically added custom options to persist for a long time. Thanks for this very well spelled out answer!
  • Shawn Abramson
    Shawn Abramson almost 9 years
    This answer works great for simple and configurable products. I am having a hard time getting it to work for grouped products. Do you have any idea how I can do that?
  • Vinai
    Vinai almost 9 years
    @ShawnAbramson For the grouped products adding the options to the individual quote items should work.
  • Shawn Abramson
    Shawn Abramson almost 9 years
    @Vinai thank you. I thought that was the way to go but I wasn't having any success. Ill have to give it another try.
  • oasisfleeting
    oasisfleeting over 8 years
    public function listenSalesConvertQuoteItemToOrderItem(Varien_Event_Observer $observer) { $orderItem = $observer->getEvent()->getOrderItem(); $quoteItem = $observer->getEvent()->getItem(); .....
  • Dushyant Joshi
    Dushyant Joshi about 8 years
    Nice Information. But how do I attach price to the item?
  • Eugene Sue
    Eugene Sue almost 8 years
    Any possibility to do same in magento 2?
  • Trliok
    Trliok almost 8 years
    @Vinai : Any possibility to do show additional options on wishlist?
  • Suresh Chikani
    Suresh Chikani over 7 years
    Code not working with configurable product. have any idea for configurable product?
  • Sanchit Gupta
    Sanchit Gupta over 7 years
    Thank you @Vinai ! It's working perfectly as desired. But I am facing an issue, Options are visible on the cart page with respective product but when I edit the product on the cart page and then click on update cart then all options are gone from the cart page. Please let me teach how do I fix this issue??? Thank You.