Magento2 | PWA | GraphQL

Display Delivery either on product page or checkout cart page Magento2


In this post we wiil check how to display product delivery date either on product detail page or checkout cart page in Magento2.

Let's start by creating custom module.

You can find complete module on Github at Magelearn_ProductDeliveryDate





Create folder inside app/code/Magelearn/ProductDeliveryDate

Add registration.php file in it:

<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magelearn_ProductDeliveryDate',
    __DIR__
);

Add composer.json file in it:

{
    "name": "magelearn/module-productdeliverydate",
    "description": "Display delivery date either on product detail page or checkout cart page in Magento2",
    "type": "magento2-module",
    "license": "proprietary",
    "version": "1.0.0",
    "authors": [
        {
            "name": "Vijay Rami",
            "email": "vijaymrami@gmail.com"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\ProductDeliveryDate\\": ""
        }
    }
}

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_ProductDeliveryDate" setup_version="1.0.0" schema_version="1.0.0">
    	<sequence>
            <module name="Magento_Catalog"/>
        </sequence>
    </module>
</config>

Now, we will add database setup script.

Create db_schema.xml file in etc folder.

this script will add new column `magelearn_delivery_date` in `sales_order``sales_order_item`quoteand `quote_item` table.

<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="sales_order" resource="sales" engine="innodb" comment="Sales Flat Order">
        <column xsi:type="timestamp" name="magelearn_delivery_date" on_update="false" nullable="true" comment="Delivery Date"/>
    </table>
    <table name="sales_order_item" resource="sales" engine="innodb" comment="Sales Flat Order Item">
        <column xsi:type="timestamp" name="magelearn_delivery_date" on_update="false" nullable="true" comment="Delivery Date"/>
    </table>
    <table name="quote" resource="checkout" engine="innodb" comment="Sales Flat Quote">
        <column xsi:type="timestamp" name="magelearn_delivery_date" on_update="false" nullable="true" comment="Delivery Date"/>
    </table>
    <table name="quote_item" resource="checkout" engine="innodb" comment="Sales Flat Quote Item">
        <column xsi:type="timestamp" name="magelearn_delivery_date" on_update="false" nullable="true" comment="Delivery Date"/>
    </table>
</schema>

To validate this db_schema.xml file we will first run below command:

php bin/magento setup:db-declaration:generate-whitelist --module-name=Magelearn_ProductDeliveryDate

This command will automatically generate db_schema_whitelist.json file inside etc folder.

First we will provide some store configurations options from admin to set product delivery date for product detail page or at checkout/cart page and some more options.

Add app/code/Magelearn/ProductDeliveryDate/etc/adminhtml/system.xml file.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="magelearn" sortOrder="1">
            <label>Magelearn Thread</label>
        </tab>
        <section id="deliverydate_options" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
            <tab>magelearn</tab>
            <label>Product Delivery Date</label>
            <resource>Magelearn_ProductDeliveryDate::deliverydate_options</resource> 
            <group id="activation" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Manage Product Delivery Date</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enable Product Delivery Date Calendar</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                 </field>  
                 <field id="required" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Make Calendar Field Required</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                 </field>  
                 <field id="daydisable" translate="label" type="multiselect" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Days To Disable</label>
                    <comment>Enter Day's e.g. saturday,sunday,monday etc.</comment>
                    <source_model>Magelearn\ProductDeliveryDate\Model\Config\Source\Disabled</source_model>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                 </field>
                <field id="datedisable" translate="label" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Dates To Disable</label>
                    <comment></comment>
                    <frontend_model>Magelearn\ProductDeliveryDate\Block\Adminhtml\Form\Field\DatePickerList</frontend_model>
                    <backend_model>Magelearn\ProductDeliveryDate\Model\Config\Backend\DatePickerList</backend_model>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field>
                <field id="mindate" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Minimum Date</label>
                    <comment>set 0 for current date. e.g if this field is zero then customer will not be able to select date less than current date. If this field is -5 then customer will not able to select date less than (currentDate-5 days).</comment>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field>  
                <field id="maxdate" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Maximum Date</label>
                    <comment>If this field is set to 15 then customer will be able to select date not more than next 15 days</comment>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field> 
                <field id="dateformat" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Date Format</label>
                    <comment>E.g. dd/mm/yy , Refer this &lt;a target="_blank" href="https://api.jqueryui.com/datepicker/#utility-formatDate"&gt;link&lt;/a&gt; for valid date formats</comment>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field>
                <field id="productwise" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Show Date Picker On Slected Products</label>
                    <comment>If this field is set to yes then for showing date picker on product you need to select yes from product attribute "Show Date Picker"</comment>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                 </field>   
            </group>
            <group id="order" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Manage Order Delivery Date</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Enable</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                 </field>  
                 <field id="required" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Make Calendar Field Required</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                 </field>  
                 <field id="daydisable" translate="label" type="multiselect" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Days To Disable</label>
                    <comment>Enter Day's e.g. saturday,sunday,monday etc.</comment>
                    <source_model>Magelearn\ProductDeliveryDate\Model\Config\Source\Disabled</source_model>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                 </field>
                 <field id="datedisable" translate="label" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Dates To Disable</label>
                    <comment></comment>
                    <frontend_model>Magelearn\ProductDeliveryDate\Block\Adminhtml\Form\Field\DatePickerList</frontend_model>
                    <backend_model>Magelearn\ProductDeliveryDate\Model\Config\Backend\DatePickerList</backend_model>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field>
                <field id="mindate" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Minimum Date</label>
                    <comment>set 0 for current date. e.g if this field is zero then customer will not be able to select date less than current date. If this field is -5 then customer will not able to select date less than (currentDate-5 days).</comment>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field>  
                <field id="maxdate" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Maximum Date</label>
                    <comment>If this field is set to 15 then customer will be able to select date not more than next 15 days</comment>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field> 
                <field id="dateformat" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Date Format</label>
                    <comment>E.g. dd/mm/yy , Refer this &lt;a target="_blank" href="https://api.jqueryui.com/datepicker/#utility-formatDate"&gt;link&lt;/a&gt; for valid date formats</comment>
                    <depends>
                        <field id="enabled">1</field>
                    </depends>
                </field>
            </group>
        </section>
    </system>
</config>
Now as per highlighted code in above file, we will add app/code/Magelearn/ProductDeliveryDate/Model/Config/Source/Disabled.php file.
<?php
namespace Magelearn\ProductDeliveryDate\Model\Config\Source;

use Magento\Framework\Locale\ListsInterface;
use Magento\Framework\Data\OptionSourceInterface;

class Disabled implements OptionSourceInterface
{
    /**
     * @var ListsInterface
     */
    protected $localeLists;

    /**
     * Disabled constructor.
     *
     * @param ListsInterface $localeLists
     */
    public function __construct(ListsInterface $localeLists)
    {
        $this->localeLists = $localeLists;
    }

    /**
     * @return array
     */
    public function toOptionArray()
    {
        $options = $this->localeLists->getOptionWeekdays();
        array_unshift($options, [
            'label' => __('No Day'),
            'value' => -1
        ]);
        return $options;
    }
}
Add app/code/Magelearn/ProductDeliveryDate/Block/Adminhtml/Form/Field/DatePickerList.php file.
<?php

namespace Magelearn\ProductDeliveryDate\Block\Adminhtml\Form\Field;

class DatePickerList extends \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray
{
    /**
     * Initialise form fields
     *
     * @return void
     */
    protected function _prepareToRender()
    {
        $this->addColumn('date', ['label' => __('Date'), 'class' => 'js-date-excluded-datepicker']);
        $this->addColumn('content', ['label' => __('Content')]);
        $this->_addAfter = false;
        $this->_addButtonLabel = __('Add Date');
        parent::_prepareToRender();
    }

    /**
     * Prepare existing row data object
     * Convert backend date format "2018-01-12" to front format "12/01/2018"
     *
     * @param \Magento\Framework\DataObject $row
     * @return void
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    protected function _prepareArrayRow(\Magento\Framework\DataObject $row)
    {
        $key = 'date';
        if (!isset($row[$key])) return;
        $rowId = $row['_id'];
        try {
            $sourceDate = \DateTime::createFromFormat('Y-m-d', $row[$key]);
            $renderedDate = $sourceDate->format('d/m/Y');
            $row[$key] = $renderedDate;
            $columnValues = $row['column_values'];
            $columnValues[$this->_getCellInputElementId($rowId, $key)] = $renderedDate;
            $row['column_values'] = $columnValues;
        } catch (\Exception $e) {
            // Just skipping error values
        }
    }

    /**
     * Get the grid and scripts contents
     *
     * @param \Magento\Framework\Data\Form\Element\AbstractElement $element
     * @return string
     */
    protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element)
    {
        $html = parent::_getElementHtml($element);

        $script = <<<JS
            <script type="text/javascript">
                // Bind click to "Add" buttons and bind datepicker to added date fields
                require(["jquery", "jquery/ui"], function (jq) {
                    jq(function(){
                        function bindDatePicker() {
                            setTimeout(function() {
                                jq(".js-date-excluded-datepicker").datepicker({
                                    dateFormat: "dd/mm/yy",
                                    minDate: 0
                                });
                            }, 50);
                        }
                        bindDatePicker();
                        jq("button.action-add").on("click", function(e) {
                            bindDatePicker();
                        });
                    });
                });
            </script>
JS;
        $html .= $script;
        return $html;
    }

}
Add app/code/Magelearn/ProductDeliveryDate/Model/Config/Backend/DatePickerList.php file.
<?php

namespace Magelearn\ProductDeliveryDate\Model\Config\Backend;

class DatePickerList extends \Magento\Config\Model\Config\Backend\Serialized\ArraySerialized
{
    /**
     * On save convert front value format like "12/01/2018" to backend format "2018-01-12"
     *
     * @return $this
     */
    public function beforeSave()
    {
        $value = [];
        $values = $this->getValue();
        foreach ((array)$values as $key => $data) {
            if ($key == '__empty') continue;
            if (!isset($data['date'])) continue;
            try {
                $date = \DateTime::createFromFormat('d/m/Y', $data['date']);
                $value[$key] = [
                    'date' => $date->format('Y-m-d'),
                    'content' => $data['content'],
                ];
            } catch (\Exception $e) {
                // Just skipping error values
            }
        }
        $this->setValue($value);
        return parent::beforeSave();
    }
}
We will also add one install schema script to add EAV attribute for product page.
Add app/code/Magelearn/ProductDeliveryDate/Setup/InstallSchema.php file.

<?php
namespace Magelearn\ProductDeliveryDate\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class InstallSchema implements InstallSchemaInterface
{
    protected $groupCollectionFactory;
    protected $attributeFactory;
    protected $eavEntitiyType;
    public function __construct(
        \Magento\Eav\Model\Entity\Type $eavEntitiyType,
        \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory $attributeFactory,
        \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory $groupCollectionFactory
    ) {
        $this->attributeFactory=$attributeFactory;
        $this->eavEntitiyType=$eavEntitiyType;
        $this->groupCollectionFactory = $groupCollectionFactory;
    }
    /**
     * {@inheritdoc}
     */
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();
        /* $attributeSetId=$this->eavEntitiyType->loadByCode('catalog_product')->getDefaultAttributeSetId(); */
        $attributeSetCollection=$this->eavEntitiyType->loadByCode('catalog_product')->getAttributeSetCollection();
        $entityTypeId=$this->eavEntitiyType->loadByCode('catalog_product')->getId();
        /* @var $model \Magento\Catalog\Model\Resource\Eav\Attribute */
        $model = $this->attributeFactory->create();
        $data['attribute_code'] = 'show_delivery_datepicker';
        $data['is_user_defined'] =1;
        $data['frontend_input'] = 'boolean';
        $data += ['is_filterable' => 0, 'is_filterable_in_search' => 0, 'apply_to' => []];
        $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']);
        $data['default_value'] = 0;
        $data['frontend_label']='Show Delivery Datepicker';
        $model->addData($data);
        $model->setEntityTypeId($entityTypeId);
        $model->setIsUserDefined(1);
        
        foreach ($attributeSetCollection as $attributeSet) {
            $attributeSetId=$attributeSet->getId();
            $groupCollection=$this->groupCollectionFactory->create()->setAttributeSetFilter($attributeSetId)->load();
            $groupCode='product-details';
            $attributeGroupId=null;
            foreach ($groupCollection as $group) {
                if ($group->getAttributeGroupCode() == $groupCode) {
                    $attributeGroupId = $group->getAttributeGroupId();
                    break;
                }
            }
            $model->setAttributeSetId($attributeSetId);
            $model->setAttributeGroupId($attributeGroupId);
            $model->save();
        }
        
        $installer->endSetup();
    }
}

Add will also add some default configuration options at app/code/Magelearn/ProductDeliveryDate/etc/config.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <deliverydate_options>
            <activation>
                <module>1</module>
                <required>1</required>
                <daydisable>sunday</daydisable>
                <datedisable></datedisable>
                <mindate>0</mindate>
                <maxdate>30</maxdate>
                <productwise>0</productwise>
                <dateformat>dd/mm/yy</dateformat>
            </activation>
            <order>
                <enabled>0</enabled>
                <required>1</required>
                <daydisable>sunday</daydisable>
                <datedisable></datedisable>
                <mindate>0</mindate>
                <maxdate>30</maxdate>
                <dateformat>dd/mm/yy</dateformat>
            </order>
        </deliverydate_options>
    </default>
</config>
Now, first we will check how to add "delivery date" on product detail page.
for that first we will modify layout for product detail page.
add app/code/Magelearn/ProductDeliveryDate/view/frontend/layout/catalog_product_view.xml
<?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="product.info.form.content">		
            <block class="Magelearn\ProductDeliveryDate\Block\Deliverydate" before="-" name="deliverydate_simple" template="Magelearn_ProductDeliveryDate::deliverydate.phtml" />
        </referenceBlock> 
        <referenceBlock name="product.info.form.options">	
            <block class="Magelearn\ProductDeliveryDate\Block\Deliverydate" before="-" name="deliverydate_config" template="Magelearn_ProductDeliveryDate::deliverydate.phtml" />
        </referenceBlock> 
    </body>
</page>

Now as per the layout file, we will add our block and template file.

Add block file at app/code/Magelearn/ProductDeliveryDate/Block/Deliverydate.php file.

<?php
namespace Magelearn\ProductDeliveryDate\Block;

class Deliverydate extends \Magento\Framework\View\Element\Template
{
    /**
     * @var \Magelearn\ProductDeliveryDate\Helper\Data
     */
    protected $_helper;

    protected $_productloader;
	
	protected $serialize;
	
	/**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $_storeManager;

    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        \Magelearn\ProductDeliveryDate\Helper\Data $helper,
        \Magento\Catalog\Model\ProductFactory $_productloader,
        \Magento\Framework\Serialize\Serializer\Json $serialize,
        \Magento\Store\Model\StoreManagerInterface $storeManager
    ) {
        $this->_helper = $helper;
        $this->_productloader = $_productloader;
		$this->serialize = $serialize;
		$this->_storeManager = $storeManager;
        parent::__construct($context);
    }

    public function getStoreConfig($key)
    {
        return $this->_helper->getCurrentStoreConfigValue($key);
    }
    public function canShow()
    {
        if ($this->_helper->getCurrentStoreConfigValue('deliverydate_options/activation/enabled')) {
            if ($this->_helper->getCurrentStoreConfigValue('deliverydate_options/activation/productwise')) {
                $id = $this->getRequest()->getParam('id');
				$current_product = $this->_productloader->create()->load($id);
                return $current_product->getShowDeliveryDatepicker();
            } else {
                return true;
            }
        }
        return false;
    }
}

As per highlighted code above, we will add our helper file at app/code/Magelearn/ProductDeliveryDate/Helper/Data.php 

<?php

namespace Magelearn\ProductDeliveryDate\Helper;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $_storeManager;
    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $_scopeConfig;
    /**
     * @var \Magento\Framework\App\Config\ValueInterface
     */
    protected $_backendModel;
    /**
     * @var \Magento\Framework\DB\Transaction
     */
    protected $_transaction;
    /**
     * @var \Magento\Framework\App\Config\ValueFactory
     */
    protected $_configValueFactory;
    /**
     * @var int $_storeId
     */
    protected $_storeId;
    /**
     * @var string $_storeCode
     */
    protected $_storeCode;
	
	protected $serialize;
	
    /**
     * @param \Magento\Framework\App\Helper\Context              $context
     * @param \Magento\Store\Model\StoreManagerInterface         $storeManager,
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
     * @param \Magento\Framework\App\Config\ValueInterface       $backendModel,
     * @param \Magento\Framework\DB\Transaction                  $transaction,
     * @param \Magento\Framework\App\Config\ValueFactory         $configValueFactory,
     * @param array                                              $data
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Framework\ObjectManagerInterface $objectmanager,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Framework\App\Config\ValueInterface $backendModel,
        \Magento\Framework\DB\Transaction $transaction,
        \Magento\Framework\App\Config\ValueFactory $configValueFactory,
        \Magento\Framework\Serialize\Serializer\Json $serialize,
        array $data = []
    ) {
        parent::__construct($context);
        $this->_storeManager = $storeManager;
        $this->_scopeConfig = $scopeConfig;
        $this->_backendModel = $backendModel;
        $this->_transaction = $transaction;
        $this->_configValueFactory = $configValueFactory;
        $this->_storeId=(int)$this->_storeManager->getStore()->getId();
        $this->_storeCode=$this->_storeManager->getStore()->getCode();
		$this->serialize = $serialize;
    }

    /**
     * Function for getting Config value of current store
     *
     * @param string $path,
     */
    public function getCurrentStoreConfigValue($path)
    {
    	return $this->_scopeConfig->getValue($path, 'store', $this->_storeCode);
    }

    /**
     * Function for setting Config value of current store
     *
     * @param string $path,
     * @param string $value,
     */
    public function setCurrentStoreConfigValue($path, $value)
    {
        $data = [
                    'path' => $path,
                    'scope' =>  'stores',
                    'scope_id' => $this->_storeId,
                    'scope_code' => $this->_storeCode,
                    'value' => $value,
                ];

        $this->_backendModel->addData($data);
        $this->_transaction->addObject($this->_backendModel);
        $this->_transaction->save();
    }
}

Add app/code/Magelearn/ProductDeliveryDate/view/frontend/templates/deliverydate.phtml file.

<?php if ($block->canShow()) { ?>
<div class="field delivery_date">
    <label class="label" for="delivery_date"><span><?= __('Delivery Date') ?></span></label>
    <div class="control">
        <input name="delivery_date" <?= $block->getStoreConfig('deliverydate_options/activation/required') == 1 ? 'data-validate="{\'required\':true}"' : '' ?> id="datepicker" title="<?= __('Delivery Date') ?>" value="" type="text">
    </div>
</div>

<script type="text/javascript">
require([
    "jquery",
    "mage/calendar"
    ], function($){
      $('#datepicker').datepicker({
          	minDate:'<?= $block->getStoreConfig('deliverydate_options/activation/mindate'); ?>',
			maxDate:'<?= $block->getStoreConfig('deliverydate_options/activation/maxdate'); ?>',
          	dayNamesMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
          	dateFormat:'<?= $block->getStoreConfig('deliverydate_options/activation/dateformat'); ?>',
          	beforeShowDay: nonWorkingDates
        });
      	if(nonWorkingDates(new Date())=='false')
		{	
			$("#datepicker").val('<?= __('Select Date') ?>');
		}

		function nonWorkingDates(date){
			var date_obj = [];
	        var day = date.getDay(), sunday = 0, monday = 1, tuesday = 2, wednesday = 3, thursday = 4, friday = 5, saturday = 6;
	        let string = $.datepicker.formatDate('yy-mm-dd', date);
	        
	        var closedDays = [<?= $block->getStoreConfig('deliverydate_options/activation/daydisable'); ?>];
	        var closedDates = '<?= $block->getStoreConfig('deliverydate_options/activation/datedisable'); ?>';
    		
    		
    		if (closedDates && closedDates.length !== 0) {
              	var closedDates_obj = JSON.parse(closedDates);
            } else {
            	var closedDates_obj = closedDates;
            }
    		
	        for (var i = 0; i < closedDays.length; i++) {
	            if (day == eval(closedDays[i])) {
	                return [false,'redday'];
	            }
	        }
	 		
	 		for (var key in closedDates_obj) {
            	// skip loop if the property is from prototype
            	if (!closedDates_obj.hasOwnProperty(key)) continue;
        
            	var obj = closedDates_obj[key];
            	for (var prop in obj) {
                	// skip loop if the property is from prototype
                	if (!obj.hasOwnProperty(prop)) continue;
        			
        			if(prop == 'date' && obj[prop] === string) {
        				return [false,'redblackday',obj['content']];
        			}
            	}
        	}

	        return [true];
	    }
    });
</script>
<?php } ?>

Now we will add our event observer file to add "delivery_date" as a custom options for order items.

Add app/code/Magelearn/ProductDeliveryDate/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="catalog_product_load_after">
        <observer name="deliverydate_catalog_product_load_after" instance="Magelearn\ProductDeliveryDate\Observer\AddDeliveryDateToProduct" shared="false" />
    </event>
    <event name="sales_model_service_quote_submit_before">
        <observer name="magelearn_deliverydate_sales_model_service_quote_submit_before" instance="Magelearn\ProductDeliveryDate\Observer\SaveOrderBeforeSalesModelQuoteObserver" />
    </event>
    <!--
    <event name="sales_convert_quote_item_to_order_item">
        <observer name="magelearn_deliverydate_sales_convert_quote_item_to_order_item" instance="Magelearn\DeliveryDate\Observer\AddOrderDeliveryDate" shared="false" />
    </event> 
     --> 
</config>

Now as per defined in events.xml file, we will add our observer file at app/code/Magelearn/ProductDeliveryDate/Observer/AddDeliveryDateToProduct.php file.

<?php
namespace Magelearn\ProductDeliveryDate\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Serialize\Serializer\Serialize;
use Magento\Framework\App\RequestInterface;

class AddDeliveryDateToProduct implements ObserverInterface
{
    protected $_helper;
    protected $_backendAuthSession;
	
    /**
	* @var RequestInterface
	*/

	protected $_request;
	protected $_logger;

    public function __construct(
        \Magelearn\ProductDeliveryDate\Helper\Data $helper,
        RequestInterface $request,
        \Magento\Backend\Model\Auth\Session $backendAuthSession,
        Serialize $serializer,
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->_helper = $helper;
        $this->_backendAuthSession = $backendAuthSession;
        $this->_request = $request;
        $this->serializer = $serializer;
		$this->_logger = $logger;
    }

    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        try {
            if (!$this->_helper->getCurrentStoreConfigValue('deliverydate_options/activation/enabled')) {
                return;
            }

            //print_r($this->_request->getParams());die;
            //print_r($this->_request->getFullActionName());die;
            $controllerModule = strtolower($this->_request->getControllerModule());
            if ($controllerModule=='magento_checkout' && ($this->_request->getActionName()=='add' || $this->_request->getActionName()=='updateItemOptions')) {
                if (!$this->_request->getParam('delivery_date')) {
                    return $this;
                }
                $product = $observer->getProduct();
                // add to the additional options array
                $additionalOptions = [];
                if ($additionalOption = $product->getCustomOption('additional_options')) {
                    try {
                        $additionalOptions = (array) $this->serializer->unserialize($additionalOption->getValue());
                    } catch (\Exception $e) {
                        $additionalOptions = json_decode($additionalOption->getValue(), true);
                    }
                }
                $additionalOptions = $this->removeDuplicate($additionalOptions);
                $deliverydate = $this->formatDeliveryDetails($this->_request->getParam('delivery_date'));
                $additionalOptions = array_merge($additionalOptions, $deliverydate);

                $mergedAdditionalOptions = $this->serializer->serialize($additionalOptions);
                $result = json_decode($mergedAdditionalOptions, true);
                if (json_last_error() !== JSON_ERROR_NONE) {
                    $mergedAdditionalOptions = json_encode($additionalOptions);
                }
				//$this->_logger->debug($mergedAdditionalOptions);die;
				
                // add the additional options array with the option code additional_options
                $product->addCustomOption('additional_options', $mergedAdditionalOptions);

                return $this;
            }
        } catch (\Exception $e) {
        }

        return $this;
    }

    public function removeDuplicate($additionalOptions)
    {
        foreach ($additionalOptions as $key=>$option) {
            if (isset($option['code']) && $option['code']=='product_delivery_date') {
                unset($additionalOptions[$key]);
            }
        }
        return $additionalOptions;
    }

    /**
     * format delivery date text
     */
    public function formatDeliveryDetails($delivery_date)
    {
        $delivery_details= $delivery_date;
        /*$delivery_details = sprintf('%s  : %s',
            __('Product Delivery Date'),
            $delivery_date
        );
        */

        $delivery_line = ((!empty($delivery_details)) ? $delivery_details . "\n" : '');

        $delivery[0] = [
            'label'       => __('Product Delivery Date'),
            'code'        => 'product_delivery_date',
            'value'       => $delivery_line,
            'print_value' => $delivery_line
        ];

        return $delivery;
    }
}

Now to display "delivery_date" in checkout/cart page, we will first modify layout file.
Add app/code/Magelearn/ProductDeliveryDate/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="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="after-shipping-method-form" xsi:type="array">
                                                            <item name="component" xsi:type="string">uiComponent</item>
                                                            <item name="displayArea" xsi:type="string">shippingAdditional</item>
                                                            <item name="children" xsi:type="array">
                                                                <item name="order_delivery_date" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Magelearn_ProductDeliveryDate/js/view/checkout/shipping/delivery-date</item>
                                                                    <item name="config" xsi:type="array">
                                                                        <item name="template" xsi:type="string">Magelearn_ProductDeliveryDate/checkout/shipping/delivery-date</item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                                
                                            </item>
                                        </item>
                                        <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="additional-payment-validators" xsi:type="array">
                                                                <item name="children" xsi:type="array">
                                                                    <item name="deliverydate-validation" xsi:type="array">
                                                                        <item name="component" xsi:type="string">Magelearn_ProductDeliveryDate/js/view/delivery-date-validation</item>
                                                                    </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Now as per highlighted code at above, we will add out JS component file and template file.

Add app/code/Magelearn/ProductDeliveryDate/view/frontend/web/template/checkout/shipping/delivery-date.html file.

<div class="deliverydate-block fieldset"
     data-bind="visible: config.order.enabled == '1'">
    <!-- <span data-bind="html: config.order.dateformat"></span> -->
    <div class="field _required">
         <label class="label"  for="order-delivery-date"><span data-bind="i18n: 'Order Delivery Date'"></span></label>
         <div class="control" >
               <input class="input-text" data-bind="afterRender:afterRender" type="text" name="order-delivery-date" aria-required="true" aria-invalid="false" id="order-delivery-date">
         </div>
     </div>
</div>

And add JS component file at app/code/Magelearn/ProductDeliveryDate/view/frontend/web/js/view/checkout/shipping/delivery-date.js

define(
    [
    'ko',
    'uiComponent',
    'Magelearn_ProductDeliveryDate/js/model/config',
    'jquery',
    "mage/calendar"

    ], function (ko,Component, config, $) {
        'use strict';
    
        return Component.extend(
            {
                defaults: {
                    template: 'Magelearn_ProductDeliveryDate/checkout/shipping/delivery-date'
                },
                initialize: function () {
                    this.afterRender.bind(this);
                    this._super();
                    //console.log(config());
                    //console.log(window.checkoutConfig.magelearnDeliveryDate.order.datedisable);
                },
                afterRender: function () {
                    $('#order-delivery-date').datepicker(
                        {
                            minDate:this.config.order.mindate,
                            maxDate:this.config.order.maxdate,
                            dayNamesMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
                            dateFormat:this.config.order.dateformat,
                            beforeShowDay: this.nonWorkingDates,
                            onSelect: this.setDeliveryDate
                        }
                    );
            

                },
                setDeliveryDate: function (date) {

                },
                nonWorkingDates: (date) => {
            		
                    let day = date.getDay();
                    let string = $.datepicker.formatDate('yy-mm-dd', date);
                    let date_obj = [];
                    //console.log(day);
                    let days =  {"sunday":0,"monday":1,"tuesday":2,"wednesday":3,"thursday":4,"friday":5,"saturday":6};

	                let blackout = window.checkoutConfig.magelearnDeliveryDate.order.datedisable; 
                    let closedDays = window.checkoutConfig.magelearnDeliveryDate.order.daydisable;
					
					if(closedDays && closedDays != null){
						var disabledDay = closedDays.split(",");
					} else {
						var disabledDay = [];
					}
                    
                    let noday = window.checkoutConfig.magelearnDeliveryDate.order.noday;
                    if(blackout || noday){
	                    function arraySearch(arr,val) {
	                        for (var i=0; i<arr.length; i++)
	                            if (arr[i].date === val)                    
	                                return arr[i].content;
	                        return false;
	                    }
	                    function arrayTooltipClass(arr,val) {
	                        for (var i=0; i<arr.length; i++)
	                            if (arr[i].date === val)                    
	                                return 'redblackday';
	                        return 'redday';
	                    }
	                    
	                    for(var i = 0; i < blackout.length; i++) {
						   var tooltipDate = blackout[i].content;
						   if(blackout[i].date === string) {
	                         date_obj.push(blackout[i].date);
						   }
						}
	                    
	                    for(var i = 0; i < blackout.length; i++) {
						   var tooltipDate = blackout[i].content;
						   if(blackout[i].date === string) {
                             date_obj.push(blackout[i].date);
						   }
						}    
						
						if(date_obj.indexOf(string) != -1 || disabledDay.indexOf(day) > -1) {
                            return [false, arrayTooltipClass(blackout,string), arraySearch(blackout,string)];
                        }
						for (const property in disabledDay) {
							if(day == disabledDay[property]) {
								return [false, arrayTooltipClass(blackout,string), arraySearch(blackout,string)];
							}
						}
	                    
	                    return [true];
                    }
                },
                config: config()
            }
        );
    }
);

Now as per highlighted code above, we will add app/code/Magelearn/ProductDeliveryDate/view/frontend/web/js/model/config.js file.

define(
    [], function () {
        'use strict';

        return function () {
            return window.checkoutConfig.magelearnDeliveryDate;
        };
    }
);

Now we will add app/code/Magelearn/ProductDeliveryDate/etc/frontend/di.xml file, to add some configuration options which will be available at window.checkoutConfig on checkout page.

Add app/code/Magelearn/ProductDeliveryDate/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">
    <type name="Magento\Checkout\Model\CompositeConfigProvider">
        <arguments>
            <argument name="configProviders" xsi:type="array">
                <item name="magelearn_deliverydate_config_provider" xsi:type="object">Magelearn\ProductDeliveryDate\Model\ConfigProvider</item>
            </argument>
        </arguments>
    </type>
</config>

As per highlighted code above, we will add app/code/Magelearn/ProductDeliveryDate/Model/ConfigProvider.php file.

<?php

namespace Magelearn\ProductDeliveryDate\Model;

use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\App\State as AppState;
use Magento\Sales\Model\AdminOrder\Create as AdminOrderCreate;

class ConfigProvider
{
	const XPATH_ACT_ENABLED		= 'deliverydate_options/activation/enabled';
    const XPATH_ACT_REQUIRED    = 'deliverydate_options/activation/required';
    const XPATH_ACT_DAYDISABLE  = 'deliverydate_options/activation/daydisable';
    const XPATH_ACT_DATEDISABLE = 'deliverydate_options/activation/datedisable';
    const XPATH_ACT_MINDATE 	= 'deliverydate_options/activation/mindate';
	const XPATH_ACT_MAXDATE 	= 'deliverydate_options/activation/maxdate';
	const XPATH_ACT_DATEFORMAT  = 'deliverydate_options/activation/dateformat';
    const XPATH_ACT_PRODUCTWISE = 'deliverydate_options/activation/productwise';
	
    const XPATH_ORDER_ENABLED     = 'deliverydate_options/order/enabled';
    const XPATH_ORDER_REQUIRED    = 'deliverydate_options/order/required';
    const XPATH_ORDER_DAYDISABLE  = 'deliverydate_options/order/daydisable';
	const XPATH_ORDER_DATEDISABLE = 'deliverydate_options/order/datedisable';
	const XPATH_ORDER_MINDATE     = 'deliverydate_options/order/mindate';
    const XPATH_ORDER_MAXDATE     = 'deliverydate_options/order/maxdate';
    const XPATH_ORDER_DATEFORMAT  = 'deliverydate_options/order/dateformat';
	
    /**
     * @var int
     */
    protected $storeId;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * @var AppState
     */
    protected $appState;

    /**
     * @var AdminOrderCreate
     */
    protected $adminOrderCreate;
	
	protected $serialize;
	
    /**
     * Config constructor.
     *
     * @param StoreManagerInterface $storeManager
     * @param ScopeConfigInterface $scopeConfig
     * @param AppState $appState
     * @param AdminOrderCreate $adminOrderCreate
     */
    public function __construct(
        StoreManagerInterface $storeManager,
        ScopeConfigInterface $scopeConfig,
        AppState $appState,
        AdminOrderCreate $adminOrderCreate,
        \Magento\Framework\Serialize\Serializer\Json $serialize
    ) {
        $this->storeManager = $storeManager;
        $this->scopeConfig = $scopeConfig;
        $this->appState = $appState;
        $this->adminOrderCreate = $adminOrderCreate;
		$this->serialize = $serialize;
    }
	
	/**
     * @return mixed
     */
    public function getOrderEnabled()
    {
        $store = $this->getStoreId();

        return $this->scopeConfig->getValue(self::XPATH_ORDER_ENABLED, ScopeInterface::SCOPE_STORE, $store);
    }
    /**
     * @return mixed
     */
    public function getOrderRequired()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ORDER_REQUIRED, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getOrderDaydisable()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ORDER_DAYDISABLE, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getOrderDatedisable()
    {
        $store = $this->getStoreId();
        $black_out = $this->scopeConfig->getValue(self::XPATH_ORDER_DATEDISABLE, ScopeInterface::SCOPE_STORE, $store);
		if (empty($black_out)) return false;
         
        $black_out_data = $this->serialize->unserialize($black_out);
         
        $black_out_options  = array();
        foreach($black_out_data as  $condtion){
            $condtionName = strtolower(str_replace(" ","_",$condtion['date']));
            $black_out_options[] = array(
                'date' =>   $condtionName,
                'content' =>   $condtion['content'],                            
            );
        }
 
        return $black_out_options;
    }
	/**
     * @return mixed
     */
    public function getOrderMindate()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ORDER_MINDATE, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getOrderMaxdate()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ORDER_MAXDATE, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getOrderDateformat()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ORDER_DATEFORMAT, ScopeInterface::SCOPE_STORE, $store);
    }
	
	/**
     * @return mixed
     */
    public function getActivationEnabled()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ACT_ENABLED, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getActivationRequired()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ACT_REQUIRED, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getActivationDaydisable()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ACT_DAYDISABLE, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getActivationDatedisable()
    {
        $store = $this->getStoreId();
        $black_out = $this->scopeConfig->getValue(self::XPATH_ACT_DATEDISABLE, ScopeInterface::SCOPE_STORE, $store);
		if (empty($black_out)) return false;
         
        $black_out_data = $this->serialize->unserialize($black_out);
         
        $black_out_options  = array();
        foreach($black_out_data as  $condtion){
            $condtionName = strtolower(str_replace(" ","_",$condtion['date']));
            $black_out_options[] = array(
                'date' =>   $condtionName,
                'content' =>   $condtion['content'],                            
            );
        }
 
        return $black_out_options;
    }
	/**
     * @return mixed
     */
    public function getActivationMindate()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ACT_MINDATE, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getActivationMaxdate()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ACT_MAXDATE, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getActivationDateformat()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ACT_DATEFORMAT, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return mixed
     */
    public function getActivationProductwise()
    {
        $store = $this->getStoreId();
        return $this->scopeConfig->getValue(self::XPATH_ACT_PRODUCTWISE, ScopeInterface::SCOPE_STORE, $store);
    }
	/**
     * @return int
     */
    public function getStoreId()
    {
        if (!$this->storeId) {
            if ($this->appState->getAreaCode() == 'adminhtml') {
                $this->storeId = $this->adminOrderCreate->getQuote()->getStoreId();
            } else {
                $this->storeId = $this->storeManager->getStore()->getStoreId();
            }
        }

        return $this->storeId;
    }

    public function getConfig()
    {
    	$order_enabled = $this->getOrderEnabled();
    	$order_required = $this->getOrderRequired();
    	$order_daydisable = $this->getOrderDaydisable();
    	$order_datedisable = $this->getOrderDatedisable();
    	$order_mindate = $this->getOrderMindate();
    	$order_maxdate = $this->getOrderMaxdate();
    	$order_dateformat = $this->getOrderDateformat();
    	
		$order_noday = 0;
        if($order_daydisable == -1) {
            $order_noday = 1;
        }
		
		$activation_enabled = $this->getActivationEnabled();
		$activation_required = $this->getActivationRequired();
		$activation_daydisable = $this->getActivationDaydisable();
		$activation_datedisable = $this->getActivationDatedisable();
		$activation_mindate = $this->getActivationMindate();
		$activation_maxdate = $this->getActivationMaxdate();
		$activation_dateformat = $this->getActivationDateformat();
		$activation_productwise = $this->getActivationProductwise();
		
		$config = [
            'magelearnDeliveryDate' => [
                'order' => [
                    'enabled' => $order_enabled,
                    'required' => $order_required,
                    'daydisable' => $order_daydisable,
                    'datedisable' => $order_datedisable,
                    'mindate' => $order_mindate,
                    'maxdate' => $order_maxdate,
                    'dateformat' => $order_dateformat,
                    'noday' => $order_noday,
                ],
                'activation' => [
                	'enabled' => $activation_enabled,
                    'required' => $activation_required,
                    'daydisable' => $activation_daydisable,
                    'datedisable' => $activation_datedisable,
                    'mindate' => $activation_mindate,
                    'maxdate' => $activation_maxdate,
                    'dateformat' => $activation_dateformat,
                    'productwise' => $activation_productwise
                ]
            ]
        ];

        return $config;
    }
}

Now as per event (sales_model_service_quote_submit_before) defined in events.xml file, we will add our observer to copy quote data to order data.

Add app/code/Magelearn/ProductDeliveryDate/Observer/SaveOrderBeforeSalesModelQuoteObserver.php file.

<?php

namespace Magelearn\ProductDeliveryDate\Observer;

use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;
use Magelearn\ProductDeliveryDate\Model\Validator;

class SaveOrderBeforeSalesModelQuoteObserver implements ObserverInterface
{
	/**
     * @var \Magento\Framework\DataObject\Copy
     */
    protected $objectCopyService;
	
    /**
     * @var Validator
     */
    private $validator;
    
	/**
     * SaveOrderBeforeSalesModelQuoteObserver constructor.
	 * @param \Magento\Framework\DataObject\Copy $objectCopyService
	 * @param Validator $validator
     */
    public function __construct(
        \Magento\Framework\DataObject\Copy $objectCopyService,
        Validator $validator
    ) {
		$this->objectCopyService = $objectCopyService;
		$this->validator = $validator;
    }
    /**
     *
     * @param  EventObserver $observer
     * @return $this
	 * @throws \Exception
     */
    public function execute(EventObserver $observer)
    {
        /* @var Magento\Sales\Model\Order $order */
        $order = $observer->getEvent()->getData('order');
        /* @var Magento\Quote\Model\Quote $quote */
        $quote = $observer->getEvent()->getData('quote');
        
        if (!$this->validator->validate($quote->getMagelearnDeliveryDate())) {
            throw new \Exception(__('Invalid Delevery Date'));
        }
        
		$order->setMagelearnDeliveryDate($quote->getMagelearnDeliveryDate());
		$this->objectCopyService->copyFieldsetToTarget('sales_convert_quote', 'to_order', $quote, $order);
		
        return $this;
    }
}

Now as per highlighted code above, we will add our validator file to validate delivery date.

Add app/code/Magelearn/ProductDeliveryDate/Model/Validator.php file.

<?php
namespace Magelearn\ProductDeliveryDate\Model;
 
use Magento\Framework\Stdlib\DateTime\DateTime;
 
class Validator
{
    /**
     * @var DateTime
     */
    private $dateTime;
 
    /**
     * Validator constructor.
     *
     * @param DateTime $dateTime
     */
    public function __construct(
        DateTime $dateTime
    ) {
        $this->dateTime = $dateTime;
    }
 
    /**
     * @param string $deliveryDate
     * @return bool
     */
    public function validate($deliveryDate)
    {
        if ($deliveryDate) {
            $deliveryDate = $this->dateTime->date('Y-m-d H:i:s', $deliveryDate);
            $now = $this->dateTime->date('Y-m-d H:i:s');
            if ($now > $deliveryDate) {
                return false;
            }
        }
 
        return true;
    }
}

Now to pass data properly from quote to order object, we will add app/code/Magelearn/ProductDeliveryDate/etc/extension_attributes.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Quote\Api\Data\CartInterface">
        <attribute code="magelearn_delivery_date" type="string" />
    </extension_attributes>
    <extension_attributes for="Magento\Sales\Api\Data\OrderInterface">
        <attribute code="magelearn_delivery_date" type="string" />
    </extension_attributes>
</config>

And app/code/Magelearn/ProductDeliveryDate/etc/fieldset.xml file.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:DataObject/etc/fieldset.xsd">
    <scope id="global">
        <fieldset id="sales_convert_quote">
            <field name="magelearn_delivery_date">
                <aspect name="to_order" />
            </field>
        </fieldset>
    </scope>
</config>

Now we will set additional options for order items by overriding Magento\Quote\Model\Quote\Item\ToOrderItem class.

For that first we will add app/code/Magelearn/ProductDeliveryDate/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="Magento\Quote\Model\Quote\Item\ToOrderItem" type="Magelearn\ProductDeliveryDate\Model\Rewrite\ToOrderItem"/>
</config>

As per highlighted code in above file, we will add app/code/Magelearn/ProductDeliveryDate/Model/Rewrite/ToOrderItem.php file.

<?php
namespace Magelearn\ProductDeliveryDate\Model\Rewrite;

use Magento\Framework\DataObject\Copy;
use Magento\Quote\Model\Quote\Item;
use Magento\Quote\Model\Quote\Address\Item as AddressItem;
use Magento\Sales\Api\Data\OrderItemInterfaceFactory as OrderItemFactory;
use Magento\Sales\Api\Data\OrderItemInterface;
use Magento\Framework\Serialize\Serializer\Serialize;

class ToOrderItem extends \Magento\Quote\Model\Quote\Item\ToOrderItem
{
    /**
     * @var Copy
     */
    protected $objectCopyService;

    /**
     * @var OrderItemFactory
     */
    protected $orderItemFactory;

    /**
     * @var \Magento\Framework\Api\DataObjectHelper
     */
    protected $dataObjectHelper;

    /**
     * @param OrderItemFactory                        $orderItemFactory
     * @param Copy                                    $objectCopyService
     * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
     */
    public function __construct(
        OrderItemFactory $orderItemFactory,
        Copy $objectCopyService,
        \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
        \Magento\Checkout\Model\Session $session,
        Serialize $serializer
    ) {
        $this->orderItemFactory = $orderItemFactory;
        $this->objectCopyService = $objectCopyService;
        $this->dataObjectHelper = $dataObjectHelper;
        $this->session = $session;
        $this->serializer = $serializer;
    }

    public function convert($item, $data = [])
    {
        $options = $item->getProductOrderOptions();
        if (!$options) {
            $options = $item->getProduct()->getTypeInstance()->getOrderOptions($item->getProduct());
        }
        $orderItemData = $this->objectCopyService->getDataFromFieldset(
            'quote_convert_item',
            'to_order_item',
            $item
        );
		if ($item instanceof \Magento\Quote\Model\Quote\Address\Item) {
            $orderItemData = array_merge(
                $orderItemData,
                $this->objectCopyService->getDataFromFieldset(
                    'quote_convert_address_item',
                    'to_order_item',
                    $item
                )
            );
        }
        if (!$item->getNoDiscount()) {
            $data = array_merge(
                $data,
                $this->objectCopyService->getDataFromFieldset(
                    'quote_convert_item',
                    'to_order_item_discount',
                    $item
                )
            );
        }

        $orderItem = $this->orderItemFactory->create();
        $this->dataObjectHelper->populateWithArray(
            $orderItem,
            array_merge($orderItemData, $data),
            \Magento\Sales\Api\Data\OrderItemInterface::class
        );
        if ($item->getOptionByCode('additional_options')) {
            try {
                $options['additional_options'] = $this->serializer->unserialize($item->getOptionByCode('additional_options')->getValue());
            } catch (\Exception $e) {
                $options['additional_options'] = json_decode($item->getOptionByCode('additional_options')->getValue(), true);
            }
        }

        $quote = $this->session->getQuote();
        $deliveryDate = $this->session->getMagelearnDeliveryDateText() ?
            $this->session->getMagelearnDeliveryDateText() :
            $quote->getMagelearnDeliveryDateText();

        if ($deliveryDate) {
            $options['additional_options'] = $this->setDeliveryDateIfNotExist($options['additional_options'] ?? [], $deliveryDate);
        }

        $orderItem->setProductOptions($options);
        if ($item->getParentItem()) {
            $orderItem->setQtyOrdered(
                $orderItemData[OrderItemInterface::QTY_ORDERED] * $item->getParentItem()->getQty()
            );
        }
        return $orderItem;
    }

    public function setDeliveryDateIfNotExist($options, $deliveryDate)
    {
        $exist = false;
        foreach ($options as $key=>$option) {
            if (isset($option['code']) && ($option['code']=='product_delivery_date' || $option['code']=='order_delivery_date')) {
                $exist = true;
            }
        }

        if (!$exist) {
            $delivery_line = ((!empty($deliveryDate)) ? $deliveryDate . "\n" : '');
            $options[] = [
                'label'       => __('Delivery Date'),
                'code'        => 'order_delivery_date',
                'value'       => $delivery_line,
                'print_value' => $delivery_line
            ];
        }
        return $options;
    }
}

Now to validate the "delivery_date" required status from going to shipping to checkout steps, we will modify Magento_Checkout/js/model/shipping-save-processor/default.js file.

For that first add app/code/Magelearn/ProductDeliveryDate/view/frontend/requirejs-config.js file.

var config = {
    "map": {
       "*": {
           "Magento_Checkout/js/model/shipping-save-processor/default" : "Magelearn_ProductDeliveryDate/js/shipping-save-processor/default"
       }
   }
}

Now add app/code/Magelearn/ProductDeliveryDate/view/frontend/web/js/shipping-save-processor/default.js file.

/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

define([
    'ko',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/resource-url-manager',
    'mage/storage',
    'Magento_Checkout/js/model/payment-service',
    'Magento_Checkout/js/model/payment/method-converter',
    'Magento_Checkout/js/model/error-processor',
    'Magento_Checkout/js/model/full-screen-loader',
    'Magento_Checkout/js/action/select-billing-address',
    'Magento_Checkout/js/model/shipping-save-processor/payload-extender',
    'jquery',
    'Magento_Ui/js/modal/alert',
    'Magelearn_ProductDeliveryDate/js/model/config',
    'mage/translate', 
    'Magento_Ui/js/model/messageList'
], function (
    ko,
    quote,
    resourceUrlManager,
    storage,
    paymentService,
    methodConverter,
    errorProcessor,
    fullScreenLoader,
    selectBillingAddressAction,
    payloadExtender,
    $,
    alert,
    config,
    $t,
    messageList
) {
    'use strict';

    return {
        /**
         * @return {jQuery.Deferred}
         */
        saveShippingInformation: function () {
            var payload;
			
			if (config().order.enabled == '1' && config().order.required == '1') {
			var delivery_date = $('#order-delivery-date').val();
          	
            if(delivery_date.length === 0){
              var message = $t('Please choose a delivery date.');
              alert({
                title: $.mage.__('Error'),
                content: $.mage.__(message)
              });
              messageList.addErrorMessage({ message: message });
              $('text[name="order-delivery-date"]').focus();// move cursor to the point
              return false;
             }
           }
         
            if (!quote.billingAddress() && quote.shippingAddress().canUseForBilling()) {
                selectBillingAddressAction(quote.shippingAddress());
            }

            payload = {
                addressInformation: {
                    'shipping_address': quote.shippingAddress(),
                    'billing_address': quote.billingAddress(),
                    'shipping_method_code': quote.shippingMethod()['method_code'],
                    'shipping_carrier_code': quote.shippingMethod()['carrier_code']
                }
            };

            payloadExtender(payload);

            fullScreenLoader.startLoader();

            return storage.post(
                resourceUrlManager.getUrlForSetShippingInformation(quote),
                JSON.stringify(payload)
            ).done(
                function (response) {
                    quote.setTotals(response.totals);
                    paymentService.setPaymentMethods(methodConverter(response['payment_methods']));
                    fullScreenLoader.stopLoader();
                }
            ).fail(
                function (response) {
                    errorProcessor.process(response);
                    fullScreenLoader.stopLoader();
                }
            );
        }
    };
});

Now as per defined in checkout_index_index.xml file, we will add our validation file once we click on place order button.

Add file at app/code/Magelearn/ProductDeliveryDate/view/frontend/web/js/view/delivery-date-validation.js

define(
    [
        'uiComponent',
        'Magento_Checkout/js/model/payment/additional-validators',
        'Magelearn_ProductDeliveryDate/js/model/delivery-date-validator'
    ],
    function (Component, additionalValidators, deliveryDateValidator) {
        'use strict';
        additionalValidators.registerValidator(deliveryDateValidator);
        return Component.extend({});
    }
);

Now as per highlighted code above, we will add our validator file at app/code/Magelearn/ProductDeliveryDate/view/frontend/web/js/model/delivery-date-validator.js 

define(
    [    
        'mage/translate', 
        'Magento_Ui/js/model/messageList',
        'Magelearn_ProductDeliveryDate/js/model/config',
        'jquery',
        'mage/url'
    ],function ($t, messageList,config,$,urlBuilder) {
        'use strict';
        return {
            validate: function () {
				if (config().order.enabled == '0') {
					return true;
				}
				var product_delivery_date = document.getElementById('order-delivery-date').value;
				
				if (config().order.required == '0' && (product_delivery_date == null
					|| product_delivery_date.length === 0 || isEmpty(product_delivery_date))) {
					return true;
				}
                let isValid = false;
                var message;
                
                let url = urlBuilder.build('deliverydate/order/set');
                $.ajax(
                    {
                        url:url, 
                        data:{
                        	'delivery_date' : product_delivery_date,
                        	'format': config().order.dateformat
                        },
                        async:false,
                        complete: function ( jqXHR ) {
                        
                            let data = $.parseJSON(jqXHR.responseText);
                        
                            if(data.success) {
                            	isValid = true;
                            }
                            else{
                                isValid = false;
                                message = data.message;
                            }
                        }
                    }
                );
                if (!isValid) {
                    messageList.addErrorMessage({ message: message });
                }
                return isValid;
            }
        }
    }
);

Now to make successful AJAX call as per above file, we will first add our route file at  app/code/Magelearn/ProductDeliveryDate/etc/frontend/routes.xml file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="deliverydate" frontName="deliverydate">
            <module name="Magelearn_ProductDeliveryDate" />
        </route>
    </router>
</config>

And will create our controller file at app/code/Magelearn/ProductDeliveryDate/Controller/Order/Set.php file.

<?php
namespace Magelearn\ProductDeliveryDate\Controller\Order;

use Magento\Framework\App\Action\Context;

class Set extends \Magento\Framework\App\Action\Action
{
    public $jsFormatToPhp = [
        'dd' => 'd',
        'mm' => 'm',
        'yy' => 'Y'
    ];

    /**
     * @var \Magento\Framework\Controller\Result\JsonFactory
     */
    protected $jsonResultFactory;

    /**
     * Constructor
     *
     * @param Context $context
     * @param \Magento\Framework\Controller\Result\JsonFactory $viewHelper
     */
    public function __construct(
        Context $context,
        \Magento\Framework\Controller\Result\JsonFactory $jsonResultFactory,
        \Magento\Checkout\Model\Session $session,
        \Magento\Quote\Model\QuoteRepository $quoteRepository,
        \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone
    ) {
        parent::__construct($context);
        $this->jsonResultFactory = $jsonResultFactory;
        $this->session = $session;
        $this->quoteRepository = $quoteRepository;
        $this->timezone = $timezone;
    }

    /**
     * Product view action
     *
     * @return \Magento\Framework\Controller\Result\Forward|\Magento\Framework\Controller\Result\Redirect
     */
    public function execute()
    {
        $data = ['success' => 0];
        try {
            if ($deliveryDate = $this->getRequest()->getParam('delivery_date')) {
                $quote = $this->session->getQuote();
                $this->session->setMagelearnDeliveryDateText($deliveryDate);
				
                $format = $this->getRequest()->getParam('format');
                foreach ($this->jsFormatToPhp as $js => $php) {
                    $format = str_replace($js, $php, $format);
                }
                $date = \DateTime::createFromFormat($format, $deliveryDate);
                $timeStamp = $this->timezone->date($date)->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT);

                $quote->setMagelearnDeliveryDate($timeStamp);

                $this->quoteRepository->save($quote);
                $data['data'] = $timeStamp;
                $data['success'] = 1;
            } else {
                $data['message'] = __('Missing required parameters');
            }
        } catch (\Exception $e) {
            $data['message'] = $e->getMessage();
        }
        $result = $this->jsonResultFactory->create();
        $result->setData($data);
        return $result;
    }
}

Now to display color for black out and disabled days in different color, we will add our css file at app/code/Magelearn/ProductDeliveryDate/view/frontend/web/css/source/_module.less

Now to display "delivery_date" properly at admin, first we will add our layout file at app/code/Magelearn/ProductDeliveryDate/view/adminhtml/layout/sales_order_view.xml

<?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">
    <body>
        <referenceContainer name="content">
            <referenceBlock name="order_info">
                <block name="magelearn_product_delivery_date" class="Magento\Sales\Block\Adminhtml\Order\View\Info" template="Magelearn_ProductDeliveryDate::order/view/deliverydate.phtml" />
            </referenceBlock>
        </referenceContainer>
        
    </body>
</page>

And as per highlighted code above, we will add our template file at app/code/Magelearn/ProductDeliveryDate/view/adminhtml/templates/order/view/deliverydate.phtml file.

<?php $order = $block->getOrder();
$orderAdminDate = $block->formatDate(
    $block->getOrderAdminDate($order->getMagelearnDeliveryDate()),
    \IntlDateFormatter::MEDIUM
);

 ?>
<tr>
    <th><?= $block->escapeHtml(__('Order Delivery Date')) ?></th>
    <td><span id="order_delivery_date"><?= $block->escapeHtml($orderAdminDate) ?></span></td>
</tr>
0 Comments On "Display Delivery either on product page or checkout cart page Magento2"

Back To Top