Magento2 | PWA | GraphQL

Add simple note text field to Magento one step checkout and display it on Admin


In this post we wiil check how to add simple note (text field) to Magento one step checkout and display it on Admin sales order view page.

We will display this simple note information by adding new tab on order view page at admin and also display it on frontend at customer's order view page.

We will also check how to add custom validation for this new field.

We will also check how to display customer's previous order's tab in order view page in Magento2 admin. 

Let's start by creating custom module.

You can find complete module on Github at Magelearn_SimpleNote










Create folder inside app/code/Magelearn/SimpleNote

Add registration.php file in it:
<?php
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_SimpleNote', __DIR__);
Add composer.json file in it:
{
    "name": "magelearn/module-simplenote",
    "description": "Add simple note field (textarea field) on checkout page magento2",
    "type": "magento2-module",
    "license": "proprietary",
    "authors": [
        {
            "name": "Mage2Gen",
            "email": "info@mage2gen.com"
        },
        {
            "name": "vijay rami",
            "email": "vijaymrami@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\SimpleNote\\": ""
        }
    }
}
Add etc/module.xml file in it:
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
	<module name="Magelearn_SimpleNote" setup_version="1.0.0">
		<sequence>
		   <module name="Magento_Sales"/>
		</sequence>
    </module>
</config>

We need to add the attributes to the quote and order entities.

Also, to have an ability to see it on the order’s grid, we need to add the column in the sales_order_grid table.

For that, we will Add a setup script classes for adding the attribute to the tables:

Add app/code/Magelearn/SimpleNote/Setup/SchemaInformation.php file:

<?php
namespace Magelearn\SimpleNote\Setup;
/**
 * Class SchemaInformation
 *
 * @codeCoverageIgnore
 */
class SchemaInformation
{
    /**
     * Attribute code
     */
    const ATTRIBUTE_SIMPLE_NOTE = 'simple_note';
}

Add app/code/Magelearn/SimpleNote/Setup/InstallData.php file.

<?php
namespace Magelearn\SimpleNote\Setup;
use Magelearn\SimpleNote\Setup\SetupService\AddSimpleNoteFieldToQuoteAndOrderService;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
/**
 * Class InstallData
 *
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * @var AddSimpleNoteFieldToQuoteAndOrderService
     */
    protected $addSimpleNoteFieldToQuoteAndOrderService;
    /**
     * InstallData constructor.
     *
     * @param AddSimpleNoteFieldToQuoteAndOrderService $addSimpleNoteFieldToQuoteAndOrderService
     */
    public function __construct(AddSimpleNoteFieldToQuoteAndOrderService $addSimpleNoteFieldToQuoteAndOrderService)
    {
        $this->addSimpleNoteFieldToQuoteAndOrderService = $addSimpleNoteFieldToQuoteAndOrderService;
    }
    /**
     * Installs data for a module
     *
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface   $context
     *
     * @return void
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();
        $this->addSimpleNoteFieldToQuoteAndOrderService->execute($setup);
        $setup->endSetup();
    }
}

Add app/code/Magelearn/SimpleNote/Setup/SetupService/AddSimpleNoteFieldToQuoteAndOrderService.php file.

<?php
namespace Magelearn\SimpleNote\Setup\SetupService;
use Magelearn\SimpleNote\Setup\SchemaInformation;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Quote\Setup\QuoteSetup;
use Magento\Quote\Setup\QuoteSetupFactory;
use Magento\Sales\Setup\SalesSetup;
use Magento\Sales\Setup\SalesSetupFactory;
/**
 * Class AddSimpleNoteFieldToQuoteAndOrderService
 *
 * @codeCoverageIgnore
 */
class AddSimpleNoteFieldToQuoteAndOrderService
{
    /**
     * @var QuoteSetupFactory
     */
    protected $quoteSetupFactory;
    /**
     * @var SalesSetupFactory
     */
    protected $salesSetupFactory;
    /**
     * AddSimpleNoteFieldToQuoteAndOrderService constructor.
     *
     * @param QuoteSetupFactory $quoteSetupFactory
     * @param SalesSetupFactory $salesSetupFactory
     */
    public function __construct(
        QuoteSetupFactory $quoteSetupFactory,
        SalesSetupFactory $salesSetupFactory
    ) {
        $this->quoteSetupFactory = $quoteSetupFactory;
        $this->salesSetupFactory = $salesSetupFactory;
    }
    /**
     * @param ModuleDataSetupInterface $dataSetup
     */
    public function execute(ModuleDataSetupInterface $dataSetup)
    {
        $attributeAttr = [
            'type' => Table::TYPE_TEXT,
            'comment' => 'Simple Note',
        ];
        $this->addAttributeToQuote(
            SchemaInformation::ATTRIBUTE_SIMPLE_NOTE,
            $attributeAttr,
            $dataSetup
        );
        $this->addAttributeToOrder(
            SchemaInformation::ATTRIBUTE_SIMPLE_NOTE,
            $attributeAttr,
            $dataSetup
        );
        $this->addAttributeToOrderGrid(
            SchemaInformation::ATTRIBUTE_SIMPLE_NOTE,
            $attributeAttr,
            $dataSetup
        );
    }
    /**
     * Add attribute to quote
     *
     * @param string $attributeCode
     * @param array $attributeAttr
     * @param ModuleDataSetupInterface $dataSetup
     */
    protected function addAttributeToQuote($attributeCode, $attributeAttr, $dataSetup)
    {
        /** @var QuoteSetup $quoteSetup */
        $quoteSetup = $this->quoteSetupFactory->create(
            [
                'resourceName' => 'quote_setup',
                'setup' => $dataSetup,
            ]
        );
        $quoteSetup->addAttribute('quote', $attributeCode, $attributeAttr);
    }
    /**
     * Add attribute to order
     *
     * @param string $attributeCode
     * @param array $attributeAttr
     * @param ModuleDataSetupInterface $dataSetup
     */
    protected function addAttributeToOrder($attributeCode, $attributeAttr, $dataSetup)
    {
        /** @var SalesSetup $salesSetup */
        $salesSetup = $this->salesSetupFactory->create(
            [
                'resourceName' => 'sales_setup',
                'setup' => $dataSetup,
            ]
        );
        $salesSetup->addAttribute('order', $attributeCode, $attributeAttr);
    }
    /**
     * Add attribute to order grid
     *
     * @param string $attributeCode
     * @param array $attributeAttr
     * @param ModuleDataSetupInterface $dataSetup
     */
    protected function addAttributeToOrderGrid($attributeCode, $attributeAttr, $dataSetup)
    {
        $dataSetup->getConnection()->addColumn(
            $dataSetup->getTable('sales_order_grid'),
            $attributeCode,
            $attributeAttr
        );
    }
}

In this post, we will do it by declaring the Magento WebAPI.
Add app/code/Magelearn/SimpleNote/etc/webapi.xml file.
Read more details about Magento webapi from here.

https://developer.adobe.com/commerce/php/development/components/web-api/services/

<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <route url="/V1/carts/mine/set-simple-note" method="PUT">
        <service class="Magelearn\SimpleNote\Api\SimpleNoteManagementInterface" method="saveSimpleNote"/>
        <resources>
            <resource ref="self" />
        </resources>
        <data>
            <parameter name="cartId" force="true">%cart_id%</parameter>
        </data>
    </route>
</routes>

Now, we will add di.xml file and define necessary nodes for simple_note field.

Add app/code/Magelearn/SimpleNote/etc/di.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magelearn\SimpleNote\Api\Data\SimpleNoteInterface" type="Magelearn\SimpleNote\Model\Data\SimpleNote" />
    <preference for="Magelearn\SimpleNote\Api\SimpleNoteManagementInterface" type="Magelearn\SimpleNote\Model\SimpleNoteManagement" />
    <virtualType name="Magento\Sales\Model\ResourceModel\Order\Grid" type="Magento\Sales\Model\ResourceModel\Grid">
        <arguments>
            <argument name="columns" xsi:type="array">
                <item name="simple_note" xsi:type="string">sales_order.simple_note</item>
            </argument>
        </arguments>
    </virtualType>
</config>

We have highlighted code in above file to save the data in the order grid table, but it is not required if you do not want to display the data in the admin order grid. 

we have added virtual type in the di.xml file.

Now, we will Add Magento Web API classes to save the data:

Add app/code/Magelearn/SimpleNote/Api/Data/SimpleNoteInterface.php file.

<?php
namespace Magelearn\SimpleNote\Api\Data;
/**
 * Interface SimpleNoteInterface
 */
interface SimpleNoteInterface
{
    /**
     * Get Simple Note
     *
     * @return string
     */
    public function getSimpleNote();
    /**
     * Set Simple Note
     *
     * @param string $simpleNote
     *
     * @return void
     */
    public function setSimpleNote($simpleNote);
}

Add app/code/Magelearn/SimpleNote/Api/SimpleNoteManagementInterface.php file.

<?php
namespace Magelearn\SimpleNote\Api;
/**
 * Interface for saving the checkout note to the quote for orders
 *
 * @api
 */
interface SimpleNoteManagementInterface
{
    /**
     * @param int $cartId
     * @param \Magelearn\SimpleNote\Api\Data\SimpleNoteInterface $simpleNote
     *
     * @return string
     */
    public function saveSimpleNote(
        $cartId,
        \Magelearn\SimpleNote\Api\Data\SimpleNoteInterface $simpleNote
    );
}

Model:

Add app/code/Magelearn/SimpleNote/Model/Data/SimpleNote.php file.

<?php
namespace Magelearn\SimpleNote\Model\Data;
use Magelearn\SimpleNote\Api\Data\SimpleNoteInterface;
use Magelearn\SimpleNote\Setup\SchemaInformation;
use Magento\Framework\Api\AbstractSimpleObject;
/**
 * Class SimpleNote
 */
class SimpleNote extends AbstractSimpleObject implements SimpleNoteInterface
{
    /**
     * @inheritdoc
     */
    public function getSimpleNote()
    {
        return $this->_get(SchemaInformation::ATTRIBUTE_SIMPLE_NOTE);
    }
    /**
     * @inheritdoc
     */
    public function setSimpleNote($simpleNote)
    {
        return $this->setData(SchemaInformation::ATTRIBUTE_SIMPLE_NOTE, $simpleNote);
    }
}

Add app/code/Magelearn/SimpleNote/Model/SimpleNoteManagement.php file.

<?php
namespace Magelearn\SimpleNote\Model;
use Magelearn\SimpleNote\Api\Data\SimpleNoteInterface;
use Magelearn\SimpleNote\Api\SimpleNoteManagementInterface;
use Magelearn\SimpleNote\Setup\SchemaInformation;
use Exception;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Quote\Api\CartRepositoryInterface;
/**
 * Class SimpleNoteManagement
 */
class SimpleNoteManagement implements SimpleNoteManagementInterface
{
    /**
     * Quote repository.
     *
     * @var CartRepositoryInterface
     */
    protected $quoteRepository;
    /**
     * SimpleNoteManagement constructor.
     *
     * @param CartRepositoryInterface $quoteRepository
     */
    public function __construct(CartRepositoryInterface $quoteRepository)
    {
        $this->quoteRepository = $quoteRepository;
    }
    /**
     * Save simple note number in the quote
     *
     * @param int $cartId
     * @param SimpleNoteInterface $simpleNote
     *
     * @return null|string
     *
     * @throws CouldNotSaveException
     * @throws NoSuchEntityException
     */
    public function saveSimpleNote(
        $cartId,
        SimpleNoteInterface $simpleNote
    ) {
        $quote = $this->quoteRepository->getActive($cartId);
        if (!$quote->getItemsCount()) {
            throw new NoSuchEntityException(__('Cart %1 doesn\'t contain products', $cartId));
        }
        $sn = $simpleNote->getSimpleNote();
        try {
            $quote->setData(SchemaInformation::ATTRIBUTE_SIMPLE_NOTE, strip_tags($sn));
            $this->quoteRepository->save($quote);
        } catch (Exception $e) {
            throw new CouldNotSaveException(__('The simple note # number could not be saved'));
        }
        return $sn;
    }
}

Now we need to save the data from quote to the order.

For that we will Add the observer and declared the event:

Add app/code/Magelearn/SimpleNote/etc/events.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="sales_model_service_quote_submit_before">
        <observer name="magelearn_add_simplenote_to_order" instance="Magelearn\SimpleNote\Observer\AddSimpleNoteToOrderObserver" />
    </event>
</config>

Observer:

Add app/code/Magelearn/SimpleNote/Observer/AddSimpleNoteToOrderObserver.php file.

<?php
namespace Magelearn\SimpleNote\Observer;
use Magelearn\SimpleNote\Setup\SchemaInformation;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Quote\Model\Quote;
use Magento\Sales\Model\Order;
/**
 * Class AddSimpleNoteToOrderObserver
 */
class AddSimpleNoteToOrderObserver implements ObserverInterface
{
    /**
     * Transfer the Simple Note from the quote to the order
     * event sales_model_service_quote_submit_before
     *
     * @param Observer $observer
     *
     * @return void
     */
    public function execute(Observer $observer)
    {
        /* @var $order Order */
        $order = $observer->getEvent()->getOrder();
        /** @var $quote Quote $quote */
        $quote = $observer->getEvent()->getQuote();
        /** @var string $simpleNote */
        $simpleNote = $quote->getData(SchemaInformation::ATTRIBUTE_SIMPLE_NOTE);
        $order->setData(SchemaInformation::ATTRIBUTE_SIMPLE_NOTE, $simpleNote);
    }
}

Frontend:

To add the simple note to the checkout and to show the field on the payment step:

Add app/code/Magelearn/SimpleNote/view/frontend/layout/checkout_index_index.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="billing-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="payment" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="beforeMethods" xsi:type="array">
                                                            <item name="children" xsi:type="array">
                                                                <item name="order-simple-note" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Magelearn_SimpleNote/js/view/order-simple-note</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                        <item name="additional-payment-validators" xsi:type="array">
                                                        	<item name="children" xsi:type="array">
                                                                    <!-- Declare your validation. START -->
                                                                    <item name="simpleNoteValidation" xsi:type="array">
                                                                        <item name="component" xsi:type="string">Magelearn_SimpleNote/js/view/isSimpleNote</item>
                                                                    </item>
                                                                    <!-- Declare your validation. END -->
                                                                </item>
                                                    	</item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

As per defined in checkout_index_index.xml file,

Add app/code/Magelearn/SimpleNote/view/frontend/web/js/view/order-simple-note.js file.

define(
    [
        'jquery',
        'ko',
        'uiComponent',
        'Magelearn_SimpleNote/js/action/save-order-simple-note',
        'Magento_Checkout/js/model/quote'
    ],
    function ($, ko, Component, saveOrderSimpleNote, quote) {
        'use strict';
        return Component.extend({
            defaults: {
                template: 'Magelearn_SimpleNote/order_simple_note',
                simpleNote: ''
            },
            initObservable: function () {
                return this._super().observe(['simpleNote'])
            },
            saveSimpleNote: function () {
                var simpleNote = this.simpleNote();
                saveOrderSimpleNote.save(simpleNote);
            }
        });
    }
);

And yes, we can use a different type of form, but in our example, we will use a text field.

Add app/code/Magelearn/SimpleNote/view/frontend/web/template/order_simple_note.html file.

<div class="order-simple-note-wrapper">
    <div class="field">
        <label class="label" for="order-simple-note">
            <span data-bind="i18n: 'Add a note to this order'"></span>
        </label>
        <input type="text"
               class="input-text"
               id="order-simple-note"
               data-bind="value: simpleNote, event: {change: saveSimpleNote}"
               placeholder="Add note"
        />
    </div>
</div>

The component (JS file) for saving checkout in the quote:

Add app/code/Magelearn/SimpleNote/view/frontend/web/js/action/save-order-simple-note.js file

define(
    [
        'jquery',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/model/url-builder',
        'Magento_Checkout/js/model/error-processor',
        'mage/url'
    ],
    function ($, quote, urlBuilder, errorProcessor, urlFormatter) {
    'use strict';
    return {
        /**
         * Save SimpleNote ibn the quote
         *
         * @param simpleNote
         */
        save: function (simpleNote) {
            if (simpleNote) {
                var quoteId = quote.getQuoteId();
                var url;
                url = urlBuilder.createUrl('/carts/mine/set-simple-note', {});
                var payload = {
                    cartId: quoteId,
                    simpleNote: {
                        simpleNote: simpleNote
                    }
                };
                if (!payload.simpleNote.simpleNote) {
                    return true;
                }
                var result = true;
                $.ajax({
                    url: urlFormatter.build(url),
                    data: JSON.stringify(payload),
                    global: false,
                    contentType: 'application/json',
                    type: 'PUT',
                    async: false
                }).done(
                    function (response) {
                        result = true;
                    }
                ).fail(
                    function (response) {
                        result = false;
                        errorProcessor.process(response);
                    }
                );
                return result;
            }
        }
    };
});

To save the data in the order grid table

Add app/code/Magelearn/SimpleNote/view/adminhtml/ui_component/sales_order_grid.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <columns name="sales_order_columns">
        <column name="simple_note">
            <settings>
                <filter>text</filter>
                <label translate="true">Simple note</label>
            </settings>
        </column>
    </columns>
</listing>

Now, we will display this simple_note field at different places in Magento admin Sales > Order > View Page.

We will also display this simple_note field by adding a new tab in left side of sales order view page.

We will also display customer's previous orders by adding new tab in left side of sales order view page.

For that, add app/code/Magelearn/SimpleNote/view/adminhtml/layout/sales_order_view.xml file.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
	<head>
		<css src="Magelearn_SimpleNote::css/questions-backend.css" />
	</head>
   <body>
       <!--add custom block -->
       <referenceBlock name="order_additional_info">
           <block class="Magelearn\SimpleNote\Block\Adminhtml\Order\View\View" name="sales_custom_view" template="Magelearn_SimpleNote::order/view/view.phtml" />
       </referenceBlock>
       <referenceContainer name="left">
       <referenceBlock name="sales_order_tabs">
           <action method="addTab">
               <argument name="name" xsi:type="string">order_note</argument>
               <argument name="block" xsi:type="string">Magelearn\SimpleNote\Block\Adminhtml\Order\View\Tab\SimpleNote</argument>
           </action>
           <action method="addTab">
	            <argument name="name" xsi:type="string">order_previous</argument>
	            <argument name="block" xsi:type="string">Magelearn\SimpleNote\Block\Adminhtml\Order\View\Tab\Previous</argument>
	        </action>
       </referenceBlock>
       </referenceContainer>
   </body>
</page>

To add order's simple_note in sales order view page:

Add app/code/Magelearn/SimpleNote/Block/Adminhtml/Order/View/View.php fille.

<?php
namespace Magelearn\SimpleNote\Block\Adminhtml\Order\View;

class View extends \Magento\Backend\Block\Template
{
    /**
     * View constructor.
     * @param \Magento\Backend\Block\Template\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param array $data
     */
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        \Magento\Framework\Registry $registry,
        array $data = []
    ) {
        $this->_coreRegistry = $registry;
        parent::__construct($context, $data);
    }

    /**
     * Retrieve order model instance
     *
     * @return \Magento\Sales\Model\Order
     */
    public function getOrder()
    {
        return $this->_coreRegistry->registry('current_order');
    }

    /**
     * Retrieve order model instance
     *
     * @return \Magento\Sales\Model\Order
     */
    public function getOrderId()
    {
        return $this->getOrder()->getEntityId();
    }
   public function getSimpleNote()
   {
       if ($this->getOrder()->getData('simple_note')) {
            return $this->getOrder()->getData('simple_note');
        }
        return null;
   }
}

Add app/code/Magelearn/SimpleNote/view/adminhtml/templates/order/view/view.phtml file.

<div class="admin__page-section-content">
    <div class="admin__page-section-item order-simple-note">
        <div class="admin__page-section-item-title">
            <span class="title"><?= $block->escapeHtml(__('Order Note')) ?></span>
        </div>
        <div class="admin__page-section-item-content"><?= $block->getSimpleNote(); ?></div>    
    </div>
</div>

To add new tab (Simple Note) in admin:

Add block file in app/code/Magelearn/SimpleNote/Block/Adminhtml/Order/View/Tab/SimpleNote.php file.

<?php

namespace Magelearn\SimpleNote\Block\Adminhtml\Order\View\Tab;

class SimpleNote extends \Magento\Backend\Block\Template implements \Magento\Backend\Block\Widget\Tab\TabInterface
{
   protected $_template = 'order/view/tab/simplenote.phtml';
   /**
    * @var \Magento\Framework\Registry
    */
   private $_coreRegistry;

   /**
    * View constructor.
    * @param \Magento\Backend\Block\Template\Context $context
    * @param \Magento\Framework\Registry $registry
    * @param array $data
    */
   public function __construct(
       \Magento\Backend\Block\Template\Context $context,
       \Magento\Framework\Registry $registry,
       array $data = []
   ) {
       $this->_coreRegistry = $registry;
       parent::__construct($context, $data);
   }

   /**
    * Retrieve order model instance
    * 
    * @return \Magento\Sales\Model\Order
    */
   public function getOrder()
   {
       return $this->_coreRegistry->registry('current_order');
   }
   /**
    * Retrieve order model instance
    *
    * @return int
    *Get current id order
    */
   public function getOrderId()
   {
       return $this->getOrder()->getEntityId();
   }

   /**
    * Retrieve order increment id
    *
    * @return string
    */
   public function getOrderIncrementId()
   {
       return $this->getOrder()->getIncrementId();
   }
   public function getSimpleNote()
   {
       if ($this->getOrder()->getData('simple_note')) {
            return $this->getOrder()->getData('simple_note');
        }
        return null;
   }
   /**
    * {@inheritdoc}
    */
   public function getTabLabel()
   {
       return __('Simple Note');
   }

   /**
    * {@inheritdoc}
    */
   public function getTabTitle()
   {
       return __('Simple Note');
   }

   /**
    * {@inheritdoc}
    */
   public function canShowTab()
   {
       return true;
   }

   /**
    * {@inheritdoc}
    */
   public function isHidden()
   {
       return false;
   }
   /**
     * Get Tab Class
     *
     * @return string
     */
    public function getTabClass()
    {
        return 'ajax only'; //load using ajax
    }

    /**
     * Get Class
     *
     * @return string
     */
    public function getClass()
    {
        return $this->getTabClass();
    }
	/**
     * Get Tab Url
     *
     * @return string
     */
    public function getTabUrl()
    {
        
        // tab url
        return $this->getUrl('simplenotetab/*/simplenotetab', ['_current' => true]);
    }
	/**
     * Tab should be loaded trough Ajax call
     *
     * @return bool
     */
    public function isAjaxLoaded()
    {
        return true;
    }
}

As per highlighted code in above file,
We will add routes, controller and template files.

Add app/code/Magelearn/SimpleNote/etc/adminhtml/routes.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/routes.xsd">
    <router id="admin">
		<route id="simplenotetab" frontName="simplenotetab">
            <module name="Magelearn_SimpleNote" />
        </route>
        <route id="previoustab" frontName="previoustab">
            <module name="Magelearn_SimpleNote" />
        </route>
    </router>
</config>

Create controller file app/code/Magelearn/SimpleNote/Controller/Adminhtml/Order/SimpleNoteTab.php file.

<?php
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magelearn\SimpleNote\Controller\Adminhtml\Order;

use Magento\Backend\App\Action;
use Magento\Sales\Api\OrderManagementInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Psr\Log\LoggerInterface;

class SimpleNoteTab extends \Magento\Sales\Controller\Adminhtml\Order
{
    /**
     * @var \Magento\Framework\View\LayoutFactory
     */
    protected $layoutFactory;

    /**
     * @param Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
     * @param \Magento\Framework\Translate\InlineInterface $translateInline
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
     * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory
     * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory
     * @param OrderManagementInterface $orderManagement
     * @param OrderRepositoryInterface $orderRepository
     * @param LoggerInterface $logger
     * @param \Magento\Framework\View\LayoutFactory $layoutFactory
     *
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
     */
    public function __construct(
        Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\App\Response\Http\FileFactory $fileFactory,
        \Magento\Framework\Translate\InlineInterface $translateInline,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
        \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory,
        \Magento\Framework\Controller\Result\RawFactory $resultRawFactory,
        OrderManagementInterface $orderManagement,
        OrderRepositoryInterface $orderRepository,
        LoggerInterface $logger,
        \Magento\Framework\View\LayoutFactory $layoutFactory
    ) {
        $this->layoutFactory = $layoutFactory;
        parent::__construct(
            $context,
            $coreRegistry,
            $fileFactory,
            $translateInline,
            $resultPageFactory,
            $resultJsonFactory,
            $resultLayoutFactory,
            $resultRawFactory,
            $orderManagement,
            $orderRepository,
            $logger
        );
    }

    /**
     * Generate order history for ajax request
     *
     * @return \Magento\Framework\Controller\Result\Raw
     */
    public function execute()
    {
        $this->_initOrder();
        $layout = $this->layoutFactory->create();
        // Yes, this is the same block class that we defined in sales_order_view.xml
        $html = $layout->createBlock('Magelearn\SimpleNote\Block\Adminhtml\Order\View\Tab\SimpleNote')
            ->toHtml();
        $this->_translateInline->processResponseBody($html);
        /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
        $resultRaw = $this->resultRawFactory->create();
        $resultRaw->setContents($html);
        return $resultRaw;
    }
}

Create template file app/code/Magelearn/SimpleNote/view/adminhtml/templates/order/view/tab/simplenote.phtml file.

<?php
/**
* @var $block \Magelearn\SimpleNote\Block\Adminhtml\Order\View\Tab\SimpleNote
*/
?>

<div class="fieldset-wrapper order-information">
   <div>
       <h2>Simple Note</h2>
   </div>
   <div class="fieldset-wrapper-title">
       <span class="title"><?php /* @escapeNotVerified */
           echo __("Information for Order's Simple note") ?></span>
   </div>
   <table class="admin__table-secondary">
       <tbody>
       <?php echo $block->getChildHtml(); ?>
       <tr>
           <th><?php /* @escapeNotVerified */
               echo __('Order ID:') ?></th>
           <td><?php echo $block->getOrderIncrementId(); ?></td>
       </tr>
       <tr>
           <th><?php /* @escapeNotVerified */
               echo __('Order Simple Note:') ?></th>
           <td><?php echo $block->getSimpleNote(); ?></td>
       </tr>
       </tbody>
   </table>
</div>

Now, to add simple_note at frontend: 
Add app/code/Magelearn/SimpleNote/etc/frontend/di.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Sales\Block\Order\Info"
                type="Magelearn\SimpleNote\Block\Order\Info"/>
</config>

As per above file,
Add block file app/code/Magelearn/SimpleNote/Block/Order/Info.php file.

<?php
 
namespace Magelearn\SimpleNote\Block\Order;
use Magento\Sales\Block\Order\Info as SalesInfo;

class Info extends SalesInfo
{
    /**
     * @var string
     */
    protected $_template = 'Magelearn_SimpleNote::order/info.phtml';
}

Now, create template file app/code/Magelearn/SimpleNote/view/frontend/templates/order/info.phtml file.

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
?>
<?php /** @var $block \Magelearn\SimpleNote\Block\Order\Info */ ?>
<?php $_order = $block->getOrder() ?>
<div class="block block-order-details-view">
    <div class="block-title">
        <strong><?= $block->escapeHtml(__('Order Information')) ?></strong>
    </div>
    <div class="block-content">
        <?php if (!$_order->getIsVirtual()) : ?>
            <div class="box box-order-shipping-address">
                <strong class="box-title"><span><?= $block->escapeHtml(__('Shipping Address')) ?></span></strong>
                <div class="box-content">
                    <address><?= /* @noEscape */ $block->getFormattedAddress($_order->getShippingAddress()) ?></address>
                </div>
            </div>

            <div class="box box-order-shipping-method">
                <strong class="box-title">
                    <span><?= $block->escapeHtml(__('Shipping Method')) ?></span>
                </strong>
                <div class="box-content">
                <?php if ($_order->getShippingDescription()) : ?>
                    <?= $block->escapeHtml($_order->getShippingDescription()) ?>
                <?php else : ?>
                    <?= $block->escapeHtml(__('No shipping information available')) ?>
                <?php endif; ?>
                </div>
            </div>
        <?php endif; ?>

        <div class="box box-order-billing-address">
            <strong class="box-title">
                <span><?= $block->escapeHtml(__('Billing Address')) ?></span>
            </strong>
            <div class="box-content">
                <address><?= /* @noEscape */ $block->getFormattedAddress($_order->getBillingAddress()) ?></address>
            </div>
        </div>
        <div class="box box-order-billing-method">
            <strong class="box-title">
                <span><?= $block->escapeHtml(__('Payment Method')) ?></span>
            </strong>
            <div class="box-content">
                <?= $block->getPaymentInfoHtml() ?>
            </div>
        </div>
        <div class="box box-order-simple-note">
            <strong class="box-title">
                <span><?= $block->escapeHtml(__('Simple Note')) ?></span>
            </strong>
            <div class="box-content">
                <?= $_order->getData('simple_note') ?>
            </div>
        </div>
    </div>
</div>

Now, we will check how to add customer's previous orders tab in Sales > Order > View Page. 

As per defined in sales_order_view.xml file,
Add Block file in Block/Adminhtml/Order/View/Tab/Previous.php

<?php

namespace Magelearn\SimpleNote\Block\Adminhtml\Order\View\Tab;


class Previous extends \Magento\Backend\Block\Template implements \Magento\Backend\Block\Widget\Tab\TabInterface
{
    /**
     * Template
     *
     * @var string
     */
    protected $_template = 'order/view/tab/previous.phtml';
	protected $orderCollectionFactory;
	protected $customers;
	protected $orderRepository;
    /**
     * Core registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $coreRegistry = null;

    /**
     * @param \Magento\Backend\Block\Template\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param array $data
     */
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        \Magento\Framework\Registry $registry,
		\Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory,
		\Magento\Customer\Model\Customer $customers,
		\Magento\Sales\Api\OrderRepositoryInterface $orderRepository,
        array $data = []
    ) {
        $this->coreRegistry = $registry;
		$this->orderCollectionFactory = $orderCollectionFactory;
		$this->customers = $customers;
		$this->orderRepository = $orderRepository;
        parent::__construct($context, $data);
    }

    /**
     * {@inheritdoc}
     */
    public function getTabLabel()
    {
        return __('Previous Orders');
    }

    /**
     * {@inheritdoc}
     */
    public function getTabTitle()
    {
        return __('Customer Previous Orders');
    }

    /**
     * {@inheritdoc}
     */
    public function canShowTab()
    {
        // For me, I wanted this tab to always show
        // You can play around with the ACL settings 
        // to selectively show later if you want
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isHidden()
    {
        // For me, I wanted this tab to always show
        // You can play around with conditions to
        // show the tab later
        return false;
    }

    /**
     * Get Tab Class
     *
     * @return string
     */
    public function getTabClass()
    {
        // I wanted mine to load via AJAX when it's selected
        // That's what this does
        return 'ajax only';
    }

    /**
     * Get Class
     *
     * @return string
     */
    public function getClass()
    {
        return $this->getTabClass();
    }

    /**
     * Get Tab Url
     *
     * @return string
     */
    public function getTabUrl()
    {
        // customtab is a adminhtml router we're about to define
        // the full route can really be whatever you want
        return $this->getUrl('previoustab/*/previoustab', ['_current' => true]);
    }
    
    /**
     * @param string $entity_id
     *
     * @return object
     */
	public function getOrder($entity_id)
	{
		return $this->orderRepository->get($entity_id);
	}
	
	public function getPreviousOrders($currentOrder)
	{
		$order = $this->coreRegistry->registry('current_order');
		
        $customerId = $order->getCustomerId();

		if(!$customerId) {
			
			$customerEmail = $order->getCustomerEmail();			
			
			$history = $this->orderCollectionFactory->create()->addFieldToSelect('entity_id')
			->addFieldToFilter(
                'customer_email',
                $customerEmail
            )
			->setOrder(
                'created_at',
                'desc'
            );
			
		
		}
		else {
            $history = $this->orderCollectionFactory->create($customerId)->addFieldToSelect('*')
            ->addAttributeToFilter('entity_id', ['nin' => $order->getId()])
            ->addAttributeToFilter('created_at', ['lteq' => $order->getCreatedAt()])
			->setOrder(
                'created_at',
                'desc'
            );
		}
		
	
		return $history->getData();
	}
    
    /**
     * Get current order
     *
     * @return object
     */
     public function getCurrentOrder()
     {
         $currentOrderId = $this->getRequest()->getParam('order_id');
 
         return $this->getOrder($currentOrderId);
     }
     
    /**
     * @param object $previousOrder
     * @param object $currentOrder
     *
     * @return string
     */
	public function getDaysSinceLastOrder($previousOrder, $currentOrder)
	{
        $currentOrderDate = new \DateTime($currentOrder->getCreatedAt());
        $previousOrderDate = new \DateTime($previousOrder->getCreatedAt());
        $interval = $currentOrderDate->diff($previousOrderDate);

        return __('%1 days ago', $interval->days);
    }
}

Create controller file at SimpleNote/Controller/Adminhtml/Order/PreviousTab.php file.

<?php

namespace Magelearn\SimpleNote\Controller\Adminhtml\Order;

use Magento\Backend\App\Action;
use Magento\Sales\Api\OrderManagementInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Psr\Log\LoggerInterface;

class PreviousTab extends \Magento\Sales\Controller\Adminhtml\Order
{
    /**
     * @var \Magento\Framework\View\LayoutFactory
     */
    protected $layoutFactory;

    /**
     * @param Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
     * @param \Magento\Framework\Translate\InlineInterface $translateInline
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
     * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory
     * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory
     * @param OrderManagementInterface $orderManagement
     * @param OrderRepositoryInterface $orderRepository
     * @param LoggerInterface $logger
     * @param \Magento\Framework\View\LayoutFactory $layoutFactory
     *
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
     */
    public function __construct(
        Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\App\Response\Http\FileFactory $fileFactory,
        \Magento\Framework\Translate\InlineInterface $translateInline,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
        \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory,
        \Magento\Framework\Controller\Result\RawFactory $resultRawFactory,
        OrderManagementInterface $orderManagement,
        OrderRepositoryInterface $orderRepository,
        LoggerInterface $logger,
        \Magento\Framework\View\LayoutFactory $layoutFactory
    ) {
        $this->layoutFactory = $layoutFactory;
        parent::__construct(
            $context,
            $coreRegistry,
            $fileFactory,
            $translateInline,
            $resultPageFactory,
            $resultJsonFactory,
            $resultLayoutFactory,
            $resultRawFactory,
            $orderManagement,
            $orderRepository,
            $logger
        );
    }

    /**
     * Generate order history for ajax request
     *
     * @return \Magento\Framework\Controller\Result\Raw
     */
    public function execute()
    {
        $this->_initOrder();
        $layout = $this->layoutFactory->create();
        // Yes, this is the same block class that we defined in sales_order_view.xml
        $html = $layout->createBlock('Magelearn\SimpleNote\Block\Adminhtml\Order\View\Tab\Previous')
            ->toHtml();
        $this->_translateInline->processResponseBody($html);
        /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
        $resultRaw = $this->resultRawFactory->create();
        $resultRaw->setContents($html);
        return $resultRaw;
    }
}

Create View file at view/adminhtml/templates/order/view/tab/previous.phtml

<?php
$currentOrder = $this->getCurrentOrder();
$previousOrders = $this->getPreviousOrders($currentOrder);
?>
<style>
.previous-order-table {
	width:100%;
}
.previous-order-table tr td {
	text-align:center;
}
</style>
<table class="previous-order-table team8-question-table">
	<thead>
		<th>Order ID</th>
		<th>Email</th>

		<th>Order Date</th>
		<th>Price</th>
		<th>Products</th>
		<th>Order Status</th>
		<th>Days Since Last Order</th>
	</thead>
<?php
$previousOrder = false;
$timeSinceLast = "";
foreach($previousOrders as $ord)
{
	$order = $this->getOrder($ord['entity_id']);
	$previousOrder = $order;

	$items = $order->getAllItems();

	?>
	<tr>
		<td><a target="_blank" href="<?= $this->getUrl("sales/order/view",["order_id" => $order->getId()]); ?>"><?= $order->getIncrementId(); ?></a></td>
		<td><?= $order->getCustomerEmail(); ?></td>
		<td><?= $order->getCreatedAt(); ?></td>
		<td><?= number_format($order->getGrandTotal(), 2, '.', '').' '.$order->getOrderCurrencyCode(); ?></td>
		<td>
			<?php foreach($items as $item)
			{ ?>
				<p><a target="_blank" href="<?= $this->getUrl("sales/order/view",["order_id" => $order->getId()]); ?>"><?= $item->getName(); ?></a></p>

			<?php }?>
		</td>
		<td><?= $order->getStatusLabel() ?> (<?= $order->getState() ?>)</td>
		<td><?= $this->getDaysSinceLastOrder($previousOrder, $currentOrder) ?></td>
	</tr>



<?php
}

?>
</table>

Now to display this previous orders tab data properly, we will add css file.

As per defined in sales_order_view.xml file,

Add CSS file 

add CSS file at view\adminhtml\web\css\questions-backend.css.

Now we will check how to display custom validation for this new field on frontend.

As per defined in checkout_index_index.xml file for custom validation,

we will add view/frontend/web/js/view/isSimpleNote.js file and will register our custom validation.

define(
    [
        'uiComponent',
        'Magento_Checkout/js/model/payment/additional-validators',
        'Magelearn_SimpleNote/js/model/isSimpleNote'
    ],
    function (Component, additionalValidators, simpleNoteValidation) {
        'use strict';
        additionalValidators.registerValidator(simpleNoteValidation);
        return Component.extend({});
    }
);

And finally, we will define our custom JS validation at
view/frontend/web/js/model/isSimpleNote.js file.

define(
    [
        'jquery',
        'mage/validation',
        'mage/translate',
        'Magento_Ui/js/model/messageList'
    ],
    function ($, validation, $t, messageList) {
        'use strict';

        return {
            validate: function () {
                var simpleNoteValidationResult = false;
                var simplenote = $('#order-simple-note').val();
                console.log(simplenote);
                if(simplenote == '' || simplenote == null){ //should use Regular expression in real store.
                    messageList.addErrorMessage({ message: $t('Please add your order notes.') });
                } else {
                	simpleNoteValidationResult = true;
                }
                return simpleNoteValidationResult;
            }
        };
    }
);

After adding above all file code, you can check that your new text field (simple_note) will be display in checkout page.

Also that field value will be available in Sales > Order View page in admin (in new tab and in detail page) and also in front-end.

You will also find a new tab "Previous Orders" are there in Sales Order view page at admin.

Also new column will be added in `sales_order`, `sales_order_grid` and `quote` table in database.


Hope, you like this post. ✌☝👍💪😀


0 Comments On "Add simple note text field to Magento one step checkout and display it on Admin"

Back To Top