In this post we wiil check how to add order delivery date and order comment in checkout page Magento2. We will also add order delivery date and order comment on checkout with Multiple shipping address form.
Let's start by creating custom module.
You can find complete module on Github at Magelearn_DeliveryDate
Create folder inside app/code/Magelearn/DeliveryDate
Add registration.php file in it:
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Magelearn_DeliveryDate', __DIR__ );
Add composer.json file in it:
{ "name": "magelearn/module-delivery-date", "description": "Order Delivery date extension will allow customer to choose preferable delivery date for order.", "type": "magento2-module", "require": {}, "authors": [ { "name": "vijay rami", "email": "vijaymrami@gmail.com" } ], "license": "proprietary", "minimum-stability": "dev", "autoload": { "files": [ "registration.php" ], "psr-4": { "Magelearn\\DeliveryDate\\": "" } } }
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_DeliveryDate" setup_version="1.0.1" /> </config>
Now, we will add database setup script.
Create db_schema.xml file in etc folder.
this script will add new column `delivery_date` and `delivery_comment` in `sales_order`, `quote` , `sales_order_grid` , `quote_address` and `sales_order_address` 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 Order Delivery Date Comment Attribute"> <column xsi:type="datetime" name="delivery_date" nullable="false" comment="Delivery Date"/> <column xsi:type="text" name="delivery_comment" nullable="false" comment="Delivery Comment"/> </table> <table name="quote" resource="checkout" engine="innodb" comment="Quote Delivery Date Comment Attribute"> <column xsi:type="datetime" name="delivery_date" nullable="false" comment="Delivery Date"/> <column xsi:type="text" name="delivery_comment" nullable="false" comment="Delivery Comment"/> </table> <table name="sales_order_grid" resource="sales" engine="innodb" comment="Sales Order Grid Delivery Date Attribute"> <column xsi:type="datetime" name="delivery_date" nullable="false" comment="Delivery Date"/> </table> <table name="quote_address" resource="checkout" engine="innodb" comment="Quote Address Delivery Date Attribute"> <column xsi:type="datetime" name="delivery_date" nullable="false" comment="Delivery Date"/> </table> <table name="sales_order_address" resource="checkout" engine="innodb" comment="Sales Order Address Delivery Date Attribute"> <column xsi:type="datetime" name="delivery_date" nullable="false" 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_DeliveryDate
This command will automatically generate db_schema_whitelist.json file inside etc folder.
<?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> <section id="magelearn_deliverydate" translate="label" type="text" sortOrder="1300" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Order Delivery Date Settings</label> <tab>sales</tab> <resource>Magelearn_DeliveryDate::deliverydate</resource> <group id="holidays" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Holidays</label> <field id="blackout_dates" translate="label" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Blackout Dates</label> <comment></comment> <frontend_model>Magelearn\DeliveryDate\Block\Adminhtml\Form\Field\DatePickerList</frontend_model> <backend_model>Magelearn\DeliveryDate\Model\Config\Backend\DatePickerList</backend_model> </field> </group> <group id="general" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Extension Settings</label> <field id="format" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Date Format:</label> <comment>yy-mm-dd</comment> </field> <field id="disabled" translate="label" type="multiselect" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Disabled Delivery Date</label> <source_model>Magelearn\DeliveryDate\Model\Config\Source\Disabled</source_model> </field> <field id="hourMin" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Delivery Hour Start:</label> <comment>Should be 8=>8AM, 9=>9AM </comment> </field> <field id="hourMax" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Delivery Hour End:</label> <comment>Should be 22=>7PM, 23=>8PM because Minutes cover 1hr </comment> </field> <field id="required_delivery_date" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Required Delivery Date</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> </section> </system> </config>
<?php namespace Magelearn\DeliveryDate\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" } ); }, 50); } bindDatePicker(); jq("button.action-add").on("click", function(e) { bindDatePicker(); }); }); }); </script> JS; $html .= $script; return $html; } }
Add app/code/Magelearn/DeliveryDate/Model/Config/Backend/DatePickerList.php file.
<?php namespace Magelearn\DeliveryDate\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(); } }
Add app/code/Magelearn/DeliveryDate/Model/Config/Source/Disabled.php file.
<?php namespace Magelearn\DeliveryDate\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; } }
Now, we will provide some pre-selected configuration values in 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> <magelearn_deliverydate> <holidays> <blackout_dates></blackout_dates> </holidays> <general> <format>yy-mm-dd</format> <disabled>-1</disabled> <hourMin>8</hourMin> <hourMax>22</hourMax> <required_delivery_date>0</required_delivery_date> </general> </magelearn_deliverydate> </default> </config>
We will add this new field by adding UI Component in Magento\Checkout\Block\Checkout\LayoutProcessor
First add app/code/Magelearn/DeliveryDate/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"> <type name="Magento\Checkout\Model\ShippingInformationManagement"> <plugin name="magelearn_save_delivery_date_in_quote" type="Magelearn\DeliveryDate\Plugin\Checkout\Model\ShippingInformationManagement" sortOrder="1"/> </type> <virtualType name="Magento\Sales\Model\ResourceModel\Order\Grid" type="Magento\Sales\Model\ResourceModel\Grid"> <arguments> <argument name="columns" xsi:type="array"> <item name="delivery_date" xsi:type="string">sales_order.delivery_date</item> </argument> </arguments> </virtualType> <type name="Magento\Checkout\Block\Checkout\LayoutProcessor"> <plugin name="magelearn_checkout_layout_processor_add_delivery_block" type="Magelearn\DeliveryDate\Plugin\Checkout\Block\LayoutProcessor" sortOrder="1"/> </type> </config>
Here in Plugin method for Magento\Checkout\Block\Checkout\LayoutProcessor we will add our custom field UI component in afterProcess method.
And in Plugin method for Magento\Checkout\Model\ShippingInformationManagement we will add our extension attributes (delivery_date) in Quote object inside beforeSaveAddressInformation method.
Add app/code/Magelearn/DeliveryDate/Plugin/Checkout/Block/LayoutProcessor.php file.
<?php namespace Magelearn\DeliveryDate\Plugin\Checkout\Block; use Magelearn\DeliveryDate\Model\Config; class LayoutProcessor { /** * @var \Magelearn\DeliveryDate\Model\Config */ protected $config; /** * LayoutProcessor constructor. * * @param Config $config */ public function __construct( Config $config ) { $this->config = $config; } /** * @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject * @param array $jsLayout * @return array */ public function afterProcess( \Magento\Checkout\Block\Checkout\LayoutProcessor $subject, array $jsLayout ) { $requiredDeliveryDate = $this->config->getRequiredDeliveryDate()?: false; $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children'] ['shippingAddress']['children']['shippingAdditional'] = [ 'component' => 'uiComponent', 'displayArea' => 'shippingAdditional', 'children' => [ 'delivery_date' => [ 'component' => 'Magelearn_DeliveryDate/js/view/delivery-date-block', 'displayArea' => 'delivery-date-block', 'deps' => 'checkoutProvider', 'dataScopePrefix' => 'delivery_date', 'children' => [ 'form-fields' => [ 'component' => 'uiComponent', 'displayArea' => 'delivery-date-block', 'children' => [ 'delivery_date' => [ 'component' => 'Magelearn_DeliveryDate/js/view/delivery-date', 'config' => [ 'customScope' => 'delivery_date', 'template' => 'ui/form/field', 'elementTmpl' => 'Magelearn_DeliveryDate/fields/delivery-date', 'options' => [], 'id' => 'delivery_date', 'data-bind' => ['datetimepicker' => true] ], 'dataScope' => 'delivery_date.delivery_date', 'label' => 'Delivery Date', 'provider' => 'checkoutProvider', 'visible' => true, 'validation' => [ 'required-entry' => $requiredDeliveryDate ], 'sortOrder' => 10, 'id' => 'delivery_date' ], 'delivery_comment' => [ 'component' => 'Magento_Ui/js/form/element/textarea', 'config' => [ 'customScope' => 'delivery_date', 'template' => 'ui/form/field', 'elementTmpl' => 'ui/form/element/textarea', 'options' => [], 'id' => 'delivery_comment' ], 'dataScope' => 'delivery_date.delivery_comment', 'label' => 'Comment', 'provider' => 'checkoutProvider', 'visible' => true, 'validation' => [], 'sortOrder' => 20, 'id' => 'delivery_comment' ] ], ], ] ] ] ]; return $jsLayout; } }
Add app/code/Magelearn/DeliveryDate/Plugin/Checkout/Model/ShippingInformationManagement.php file.
<?php namespace Magelearn\DeliveryDate\Plugin\Checkout\Model; use Magento\Quote\Model\QuoteRepository; use Magento\Checkout\Api\Data\ShippingInformationInterface; class ShippingInformationManagement { /** * @var QuoteRepository */ protected $quoteRepository; /** * ShippingInformationManagement constructor. * * @param QuoteRepository $quoteRepository */ public function __construct( QuoteRepository $quoteRepository ) { $this->quoteRepository = $quoteRepository; } /** * @param \Magento\Checkout\Model\ShippingInformationManagement $subject * @param $cartId * @param ShippingInformationInterface $addressInformation */ public function beforeSaveAddressInformation( \Magento\Checkout\Model\ShippingInformationManagement $subject, $cartId, ShippingInformationInterface $addressInformation ) { $extAttributes = $addressInformation->getExtensionAttributes(); $deliveryDate = $extAttributes->getDeliveryDate(); $deliveryComment = $extAttributes->getDeliveryComment(); $quote = $this->quoteRepository->getActive($cartId); $quote->setDeliveryDate($deliveryDate); $quote->setDeliveryComment($deliveryComment); } }
Now we will add app/code/Magelearn/DeliveryDate/etc/extension_attributes.xml file to add `delivery_date` and `delivery_comment` attributes as extension_attributes for ShippingInformationManagement
<?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\Checkout\Api\Data\ShippingInformationInterface"> <attribute code="delivery_date" type="string"/> <attribute code="delivery_comment" type="string"/> </extension_attributes> </config>
Add app/code/Magelearn/DeliveryDate/etc/fieldset.xml file.
<?xml version="1.0"?> <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="delivery_comment"> <aspect name="to_order"/> </field> <field name="delivery_date"> <aspect name="to_order" /> </field> </fieldset> </scope> </config>
Now we will add app/code/Magelearn/DeliveryDate/etc/events.xml file to save Quote data into order data.
<?xml version="1.0" encoding="UTF-8"?> <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_delivery_date" instance="Magelearn\DeliveryDate\Observer\SalesModelServiceQuoteSubmitBefore"/> </event> </config>
Add app/code/Magelearn/DeliveryDate/Observer/SalesModelServiceQuoteSubmitBefore.php file.
<?php namespace Magelearn\DeliveryDate\Observer; use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Event\ObserverInterface; use Magelearn\DeliveryDate\Model\Validator; class SalesModelServiceQuoteSubmitBefore implements ObserverInterface { /** * @var Validator */ private $validator; /** * @var \Magento\Framework\DataObject\Copy */ protected $objectCopyService; /** * SalesModelServiceQuoteSubmitBefore constructor. * * @param Validator $validator * @param \Magento\Framework\DataObject\Copy $objectCopyService */ public function __construct( Validator $validator, \Magento\Framework\DataObject\Copy $objectCopyService ) { $this->validator = $validator; $this->objectCopyService = $objectCopyService; } /** * @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->getDeliveryDate())) { throw new \Exception(__('Invalid Delevery Date')); } $order->setDeliveryDate($quote->getDeliveryDate()); $order->setDeliveryComment($quote->getDeliveryComment()); $this->objectCopyService->copyFieldsetToTarget('sales_convert_quote', 'to_order', $quote, $order); return $this; } }
Add app/code/Magelearn/DeliveryDate/Model/Validator.php file.
<?php namespace Magelearn\DeliveryDate\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 save this checkout field properly into database, we will pass this field value into payload >> addressInformation >> extension_attributes array.
For that we have to override Magento_Checkout/js/model/shipping-save-processor/default.js file.
Add app/code/Magelearn/DeliveryDate/view/frontend/requirejs-config.js file.
var config = { "map": { "*": { 'Magento_Checkout/js/model/shipping-save-processor/default': 'Magelearn_DeliveryDate/js/model/shipping-save-processor/default' } }, config: { mixins: { 'Magento_Checkout/js/view/shipping': { 'Magelearn_DeliveryDate/js/mixin/shipping-mixin': true }, 'Amazon_Payment/js/view/shipping': { 'Magelearn_DeliveryDate/js/mixin/shipping-mixin': true } } } };
Here we have also defined our shipping mixin to validate the delivery date.
You can read more about mixins at here:
https://developer.adobe.com/commerce/frontend-core/javascript/mixins/
Add app/code/Magelearn/DeliveryDate/view/frontend/web/js/mixin/shipping-mixin.js
define( [ 'jquery', 'ko' ], function ( $, ko ) { 'use strict'; return function (target) { return target.extend({ setShippingInformation: function () { if (this.validateDeliveryDate()) { this._super(); } }, validateDeliveryDate: function() { this.source.set('params.invalid', false); this.source.trigger('delivery_date.data.validate'); if (this.source.get('params.invalid')) { return false; } return true; } }); } } );
Now add app/code/Magelearn/DeliveryDate/view/frontend/web/js/model/shipping-save-processor/default.js file.
/*global define,alert*/ define( [ 'jquery', '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' ], function ( $, ko, quote, resourceUrlManager, storage, paymentService, methodConverter, errorProcessor, fullScreenLoader, selectBillingAddressAction ) { 'use strict'; return { saveShippingInformation: function () { var payload; if (!quote.billingAddress()) { 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, extension_attributes:{ delivery_date: $('[name="delivery_date"]').val(), delivery_comment: $('[name="delivery_comment"]').val() } } }; 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 to proceed further, we will pass these additional variables values into window.checkoutconfig which will be used in further calculation.
For that we will modify etc/frontend/di.xml file and pass additional argument in Magento\Checkout\Model\CompositeConfigProvider.
Add app/code/Magelearn/DeliveryDate/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="delivery_date_config_provider" xsi:type="object">Magelearn\DeliveryDate\Model\DeliveryDateConfigProvider</item> </argument> </arguments> </type> <!-- Add Delivery Date block to Multishipping --> <type name="Magento\Multishipping\Block\Checkout\Shipping"> <plugin name="delivery_date_items_box" type="Magelearn\DeliveryDate\Plugin\Multishipping\Block\ItemsBox" sortOrder="0"/> </type> <!-- Save Delivery Date data to quote --> <type name="Magento\Multishipping\Model\Checkout\Type\Multishipping"> <plugin name="magelearn_delivery_date_save_data" type="Magelearn\DeliveryDate\Model\Type\Plugin\Multishipping"/> </type> </config>
Now add app/code/Magelearn/DeliveryDate/Model/DeliveryDateConfigProvider.php file.
<?php namespace Magelearn\DeliveryDate\Model; use Magento\Checkout\Model\ConfigProviderInterface; use Magelearn\DeliveryDate\Model\Config; class DeliveryDateConfigProvider implements ConfigProviderInterface { /** * @var \Magelearn\DeliveryDate\Model\Config */ protected $config; /** * DeliveryDateConfigProvider constructor. * * @param Config $config */ public function __construct( Config $config ) { $this->config = $config; } /** * {@inheritdoc} */ public function getConfig() { return $this->config->getConfig(); } }
As per highlighted in above code, we will add app/code/Magelearn/DeliveryDate/Model/Config.php file.
Add app/code/Magelearn/DeliveryDate/Model/Config.php file.
<?php namespace Magelearn\DeliveryDate\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 Config { const XPATH_FORMAT = 'magelearn_deliverydate/general/format'; const XPATH_DISABLED = 'magelearn_deliverydate/general/disabled'; const XPATH_HOURMIN = 'magelearn_deliverydate/general/hourMin'; const XPATH_HOURMAX = 'magelearn_deliverydate/general/hourMax'; const XPATH_REQUIRED_DELIVERY_DATE = 'magelearn_deliverydate/general/required_delivery_date'; const XPATH_BLACKOUT_DATES = 'magelearn_deliverydate/holidays/blackout_dates'; /** * @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 getFormat() { $store = $this->getStoreId(); return $this->scopeConfig->getValue(self::XPATH_FORMAT, ScopeInterface::SCOPE_STORE, $store); } /** * @return mixed */ public function getDisabled() { $store = $this->getStoreId(); return $this->scopeConfig->getValue(self::XPATH_DISABLED, ScopeInterface::SCOPE_STORE, $store); } /** * @return mixed */ public function getBlackoutDates() { $store = $this->getStoreId(); $black_out = $this->scopeConfig->getValue(self::XPATH_BLACKOUT_DATES, 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 getHourMin() { $store = $this->getStoreId(); return $this->scopeConfig->getValue(self::XPATH_HOURMIN, ScopeInterface::SCOPE_STORE, $store); } /** * @return mixed */ public function getHourMax() { $store = $this->getStoreId(); return $this->scopeConfig->getValue(self::XPATH_HOURMAX, ScopeInterface::SCOPE_STORE, $store); } /** * @return mixed */ public function getRequiredDeliveryDate() { $store = $this->getStoreId(); return (bool) $this->scopeConfig->getValue(self::XPATH_REQUIRED_DELIVERY_DATE, 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; } /** * {@inheritdoc} */ public function getConfig() { $disabled = $this->getDisabled(); $hourMin = $this->getHourMin(); $hourMax = $this->getHourMax(); $format = $this->getFormat(); $blackout = $this->getBlackoutDates(); $noday = 0; if($disabled == -1) { $noday = 1; } $config = [ 'shipping' => [ 'delivery_date' => [ 'format' => $format, 'disabled' => $disabled, 'noday' => $noday, 'hourMin' => $hourMin, 'hourMax' => $hourMax, 'blackout' => $blackout ] ] ]; return $config; } }
Now as per defined in LayoutProcessor.php file, we will create our JS component and accordingly will create HTML file.
Add app/code/Magelearn/DeliveryDate/view/frontend/web/js/view/delivery-date-block.js file.
define([ 'jquery', 'ko', 'Magento_Ui/js/form/form' ], function ($, ko, Component) { 'use strict'; return Component.extend({ defaults: { template: 'Magelearn_DeliveryDate/delivery-date-block' } }); });
Now, we will add our template file at app/code/Magelearn/DeliveryDate/view/frontend/web/template/delivery-date-block.html
<!-- ko foreach: getRegion('delivery-date-block') --> <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko-->
here in getRegion() we will define region name as per defined in LayoutProcessor.php displayArea node.
We will also define our delivery-date JS component as per defined in LayoutProcessor.php file.
Add app/code/Magelearn/DeliveryDate/view/frontend/web/js/view/delivery-date.js file.
define([ 'jquery', 'ko', 'Magento_Ui/js/form/element/abstract', 'mage/calendar' ], function ($, ko, Component) { 'use strict'; return Component.extend({ initialize: function () { this._super(); var prevValue = window.checkoutConfig.quoteData.delivery_date; var defaultValue = window.checkoutConfig.shipping.delivery_date.default_delivery_date; var disabled = window.checkoutConfig.shipping.delivery_date.disabled; var blackout = window.checkoutConfig.shipping.delivery_date.blackout; var noday = window.checkoutConfig.shipping.delivery_date.noday; var hourMin = parseInt(window.checkoutConfig.shipping.delivery_date.hourMin); var hourMax = parseInt(window.checkoutConfig.shipping.delivery_date.hourMax); var format = window.checkoutConfig.shipping.delivery_date.format; if(!format) { format = 'yy-mm-dd'; } var disabledDay = disabled.split(",").map(function(item) { return parseInt(item, 10); }); console.log(blackout); ko.bindingHandlers.datetimepicker = { init: function (element, valueAccessor, allBindingsAccessor) { var $el = $(element); //initialize datetimepicker var options = { minDate: 0, dateFormat:format, hourMin: hourMin, hourMax: hourMax, beforeShowDay: function(date) { if(blackout || noday){ var string = $.datepicker.formatDate('yy-mm-dd', date); var day = date.getDay(); var date_obj = []; 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); } } if(date_obj.indexOf(string) != -1 || disabledDay.indexOf(day) > -1) { return [false, arrayTooltipClass(blackout,string), arraySearch(blackout,string)]; } else { return [true]; } } } }; $el.datetimepicker(options); if(prevValue){ $el.datepicker("setDate", prevValue); } else { $el.datepicker("setDate", defaultValue); } var writable = valueAccessor(); if (!ko.isObservable(writable)) { var propWriters = allBindingsAccessor()._ko_property_writers; if (propWriters && propWriters.datetimepicker) { writable = propWriters.datetimepicker; } else { return; } } writable($(element).datetimepicker("getDate")); }, update: function (element, valueAccessor) { var widget = $(element).data("DateTimePicker"); //when the view model is updated, update the widget if (widget) { var date = ko.utils.unwrapObservable(valueAccessor()); widget.date(date); } } }; return this; } }); });
Also as per defined in LayoutProcessor.php file, we will add our elementTmpl file at app/code/Magelearn/DeliveryDate/view/frontend/web/template/fields/delivery-date.html
<input class="input-text" type="text" data-bind=" event: {change: userChanges}, hasFocus: focused, value: value, datetimepicker: true, attr: { name: inputName, placeholder: placeholder, 'aria-describedby': noticeId, disabled: disabled }" readonly="true"/>
Now, we will check how to display this additional information on frontend.
For that add app/code/Magelearn/DeliveryDate/etc/frontend/events.xml file.
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="core_layout_render_element"> <observer name="magelearn_delivery_date_add_to_order_view" instance="Magelearn\DeliveryDate\Observer\AddHtmlToOrderShippingBlock" /> </event> <event name="checkout_type_multishipping_create_orders_single"> <observer name="magelearn_delivery_date_create_order" instance="Magelearn\DeliveryDate\Observer\MultishippingEventCreateOrdersObserver" shared="false" /> </event> </config>
As per defined in etc/frontend/events.xml file we will create our observer file.
Add app/code/Magelearn/DeliveryDate/Observer/AddHtmlToOrderShippingBlock.php file.
<?php namespace Magelearn\DeliveryDate\Observer; use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Event\ObserverInterface; use Magento\Framework\View\Element\TemplateFactory; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Store\Model\ScopeInterface; class AddHtmlToOrderShippingBlock implements ObserverInterface { /** * @var TemplateFactory */ protected $templateFactory; /** * @var TimezoneInterface */ private $timezone; /** * AddHtmlToOrderShippingBlock constructor. * * @param TemplateFactory $templateFactory * @param TimezoneInterface $timezone */ public function __construct( TemplateFactory $templateFactory, TimezoneInterface $timezone ) { $this->templateFactory = $templateFactory; $this->timezone = $timezone; } /** * @param EventObserver $observer * @return $this */ public function execute(EventObserver $observer) { if($observer->getElementName() == 'sales.order.info') { $orderShippingViewBlock = $observer->getLayout()->getBlock($observer->getElementName()); $order = $orderShippingViewBlock->getOrder(); if($order->getDeliveryDate() != '0000-00-00 00:00:00') { $formattedDate = $this->timezone->formatDateTime( $order->getDeliveryDate(), \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM, null, $this->timezone->getConfigTimezone( ScopeInterface::SCOPE_STORE, $order->getStore()->getCode() ) ); } else { $formattedDate = __('N/A'); } /** @var \Magento\Framework\View\Element\Template $deliveryDateBlock */ $deliveryDateBlock = $this->templateFactory->create(); $deliveryDateBlock->setDeliveryDate($formattedDate); $deliveryDateBlock->setDeliveryComment($order->getDeliveryComment()); $deliveryDateBlock->setTemplate('Magelearn_DeliveryDate::order_info_shipping_info.phtml'); $html = $observer->getTransport()->getOutput() . $deliveryDateBlock->toHtml(); $observer->getTransport()->setOutput($html); } return $this; } }
Now we will add our template file at view/frontend/templates/order_info_shipping_info.phtml file.
<div id="delivery-date"> <strong><?= __('Delivery Date') ?></strong> <span class="price"><?= $block->getDeliveryDate() ?></span> </div> <div id="delivery-comment"> <strong><?= __('Comment') ?>:</strong> <br/> <span class="price"><?= $block->getDeliveryComment() ?></span> </div> <script type="text/javascript"> require( ['jquery'], function($) { var element = $('#delivery-date').detach(); $('.box-order-shipping-method').append(element); var element = $('#delivery-comment').detach(); $('.box-order-shipping-method').append(element); } ); </script>
Now we will check how to display this fields information at admin side.
First we will modify with adminhtml layout files.
Add view/adminhtml/layout/sales_order_create_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"> <head> <css src="Magelearn_DeliveryDate::css/datetimepicker.css"/> </head> <body> <referenceBlock name="shipping_method"> <block class="Magelearn\DeliveryDate\Block\Adminhtml\DeliveryDate" template="Magelearn_DeliveryDate::order/create/delivery_date.phtml" name="delivery_date" as="delivery_date" after="-"/> </referenceBlock> </body> </page>
Add view/adminhtml/layout/sales_order_create_load_block_shipping_method.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="shipping_method"> <block class="Magelearn\DeliveryDate\Block\Adminhtml\DeliveryDate" template="Magelearn_DeliveryDate::order/create/delivery_date.phtml" name="delivery_date" as="delivery_date" after="-"/> </referenceBlock> </body> </page>
Now we will create our block file at app/code/Magelearn/DeliveryDate/Block/Adminhtml/DeliveryDate.php file.
<?php namespace Magelearn\DeliveryDate\Block\Adminhtml; use Magento\Backend\Block\Template; use Magento\Backend\Block\Template\Context; use Magelearn\DeliveryDate\Model\Config; use Magento\Framework\Serialize\Serializer\Json; class DeliveryDate extends Template { /** * @var Config */ private $config; /** * @var Json */ private $json; /** * DeliveryDate constructor. * * @param Context $context * @param Config $config * @param Json $json * @param array $data */ public function __construct( Context $context, Config $config, Json $json, array $data = [] ) { $this->config = $config; $this->json = $json; parent::__construct($context, $data); } /** * @return string */ public function getConfig() { return $this->json->serialize($this->config->getConfig()); } /** * @return mixed */ public function getRequiredDeliveryDate() { return $this->config->getRequiredDeliveryDate(); } }
Now we will add our template file at view/adminhtml/templates/order/create/delivery_date.phtml
<?php /** @var \Magelearn\DeliveryDate\Block\Adminhtml\DeliveryDate $block */ ?> <div class="admin__field field-delivery_date"> <label for="delivery_date" class="admin__field-label"><span><?= __('Delivery Date') ?></span></label> <div class="admin__field-control"> <input id="delivery_date" name="delivery_date" readonly="true" class="admin__control-input <?php echo ($block->getRequiredDeliveryDate()) ? 'required-entry' : ''; ?>" > </div> </div> <div class="admin__field field-comment"> <label for="delivery_comment" class="admin__field-label"><span><?= __('Comment') ?></span></label> <div class="admin__field-control"> <textarea id="delivery_comment" name="delivery_comment" class="admin__control-textarea" cols="5" rows="5"></textarea> </div> </div> <script type="text/javascript"> require([ 'jquery', 'mage/calendar' ], function ($) { $(document).ready(function () { var config = <?= $block->getConfig() ?>; var disabled = config.shipping.delivery_date.disabled; var blackout = config.shipping.delivery_date.blackout; var noday = config.shipping.delivery_date.noday; var hourMin = parseInt(config.shipping.delivery_date.hourMin); var hourMax = parseInt(config.shipping.delivery_date.hourMax); var format = config.shipping.delivery_date.format; if(!format) { format = 'yy-mm-dd'; } var disabledDay = disabled.split(",").map(function(item) { return parseInt(item, 10); }); var options = { minDate: 0, dateFormat:format, hourMin: hourMin, hourMax: hourMax, beforeShowDay: function(date) { if(blackout || noday){ var string = $.datepicker.formatDate('yy-mm-dd', date); var day = date.getDay(); var date_obj = []; 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); } } if(date_obj.indexOf(string) != -1 || disabledDay.indexOf(day) > -1) { return [false, arrayTooltipClass(blackout,string), arraySearch(blackout,string)]; } else { return [true]; } } } }; $('#delivery_date').datetimepicker(options); }); }); </script>Now we will add our adminhtml events at etc/adminhtml/events.xml file.
<?xml version="1.0" encoding="UTF-8"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="core_layout_render_element"> <observer name="magelearn_delivery_date_add_to_order_view" instance="Magelearn\DeliveryDate\Observer\AddHtmlToOrderShippingView" /> </event> <event name="adminhtml_sales_order_create_process_data"> <observer name="magelearn_adminhtml_sales_order_create_process_data" instance="Magelearn\DeliveryDate\Observer\AdminhtmlSalesOrderCreateProcessData"/> </event> </config>Now we will add our observers for these events.Add app/code/Magelearn/DeliveryDate/Observer/AddHtmlToOrderShippingView.php file.
<?php namespace Magelearn\DeliveryDate\Observer; use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Event\ObserverInterface; use Magento\Framework\View\Element\TemplateFactory; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Store\Model\ScopeInterface; class AddHtmlToOrderShippingView implements ObserverInterface { /** * @var TemplateFactory */ protected $templateFactory; /** * @var TimezoneInterface */ private $timezone; /** * AddHtmlToOrderShippingBlock constructor. * * @param TemplateFactory $templateFactory * @param TimezoneInterface $timezone */ public function __construct( TemplateFactory $templateFactory, TimezoneInterface $timezone ) { $this->templateFactory = $templateFactory; $this->timezone = $timezone; } /** * @param EventObserver $observer * @return $this */ public function execute(EventObserver $observer) { if($observer->getElementName() == 'order_shipping_view') { $orderShippingViewBlock = $observer->getLayout()->getBlock($observer->getElementName()); $order = $orderShippingViewBlock->getOrder(); if($order->getDeliveryDate() != '0000-00-00 00:00:00') { $formattedDate = $this->timezone->formatDateTime( $order->getDeliveryDate(), \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM, null, $this->timezone->getConfigTimezone( ScopeInterface::SCOPE_STORE, $order->getStore()->getCode() ) ); } else { $formattedDate = __('N/A'); } /** @var \Magento\Framework\View\Element\Template $deliveryDateBlock */ $deliveryDateBlock = $this->templateFactory->create(); $deliveryDateBlock->setDeliveryDate($formattedDate); $deliveryDateBlock->setDeliveryComment($order->getDeliveryComment()); $deliveryDateBlock->setTemplate('Magelearn_DeliveryDate::order_info_shipping_info.phtml'); $html = $observer->getTransport()->getOutput() . $deliveryDateBlock->toHtml(); $observer->getTransport()->setOutput($html); } return $this; } }Add app/code/Magelearn/DeliveryDate/Observer/AdminhtmlSalesOrderCreateProcessData.php file.
<?php namespace Magelearn\DeliveryDate\Observer; use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Event\ObserverInterface; class AdminhtmlSalesOrderCreateProcessData implements ObserverInterface { /** * @param EventObserver $observer * @return $this */ public function execute(EventObserver $observer) { $requestData = $observer->getRequest(); $deliveryDate = isset($requestData['delivery_date']) ? $requestData['delivery_date'] : null; $deliveryComment = isset($requestData['delivery_comment']) ? $requestData['delivery_comment'] : null; /** @var \Magento\Sales\Model\AdminOrder\Create $orderCreateModel */ $orderCreateModel = $observer->getOrderCreateModel(); $quote = $orderCreateModel->getQuote(); $quote->setDeliveryDate($deliveryDate); $quote->setDeliveryComment($deliveryComment); return $this; } }
Now as per the high-lighted code in AddHtmlToOrderShippingView.php file, we will add template file at view/adminhtml/templates/order_info_shipping_info.phtml
<section class="admin__page-section" id="delivery-date-content"> <div class="admin__page-section-title"> <span class="title"><?= $block->escapeHtml(__('Delivery Date Information')) ?></span> </div> <div class="admin__page-section-content"> <div id="delivery-date"> <strong><?= __('Delivery Date') ?></strong> <span><?= $block->getDeliveryDate() ?></span> </div> <div id="delivery-comment"> <strong><?= __('Comment') ?>:</strong> <br/> <?= $block->getDeliveryComment() ?> </div> </div> </section> <script type="text/javascript"> require( ['jquery'], function($) { $('#delivery-date-content').insertAfter($('.order-view-billing-shipping')); } ); </script>
Now we will check how to display this delivery_date field on multishipping address form.
As per defined in etc/frontend/di.xml file, we will add our plugin class for multishipping address.
Add app/code/Magelearn/DeliveryDate/Plugin/Multishipping/Block/ItemsBox.php file.
<?php namespace Magelearn\DeliveryDate\Plugin\Multishipping\Block; use Magento\Framework\DataObject; use Magento\Multishipping\Block\Checkout\Shipping as ShippingBlock; use Magelearn\DeliveryDate\Helper\DeliveryDate as DeliveryDateHelper; /** * Multishipping items box plugin */ class ItemsBox { protected $helper; /** * ItemsBox constructor. * @param DeliveryDateHelper $helper */ public function __construct(DeliveryDateHelper $helper) { $this->helper = $helper; } /** * Get items box message text for multishipping * * @param ShippingBlock $subject * @param string $itemsBoxText * @param DataObject $addressEntity * * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterGetItemsBoxTextAfter(ShippingBlock $subject, $itemsBoxText, DataObject $addressEntity) { return $this->helper->getInline($addressEntity) . $itemsBoxText; } }
Now, we will add our helper file at app/code/Magelearn/DeliveryDate/Helper/DeliveryDate.php
<?php namespace Magelearn\DeliveryDate\Helper; use Magento\Framework\App\Helper\Context; use Magento\Framework\View\LayoutFactory; class DeliveryDate extends \Magento\Framework\App\Helper\AbstractHelper { /** * @var \Magento\Framework\View\LayoutFactory */ protected $_layoutFactory; /** * Next id for edit delivery date block * * @var int */ protected $_nextId = 0; /** * DeliveryDate constructor. * @param Context $context * @param LayoutFactory $layoutFactory */ public function __construct( Context $context, LayoutFactory $layoutFactory ) { $this->_layoutFactory = $layoutFactory; parent::__construct($context); } public function getInline(\Magento\Framework\DataObject $entity) { return $this->_layoutFactory->create()->createBlock(\Magelearn\DeliveryDate\Block\DeliveryDate\Inline::class) ->setId('delivery_date_form_' . $this->_nextId++) //->setDontDisplayContainer($dontDisplayContainer) ->setEntity($entity) //->setCheckoutType($type) ->toHtml(); } }
As per highlighted code above, we will add app/code/Magelearn/DeliveryDate/Block/DeliveryDate/Inline.php file.
<?php namespace Magelearn\DeliveryDate\Block\DeliveryDate; use Magento\Framework\View\Element\Template; use Magelearn\DeliveryDate\Model\Config; class Inline extends \Magento\Framework\View\Element\Template { /** * @var mixed */ protected $_entity = null; /** * @var string */ protected $_template = 'Magelearn_DeliveryDate::inline.phtml'; /** * @var Config */ private $config; public function __construct( Template\Context $context, Config $config, array $data = [] ) { parent::__construct($context, $data); $this->config = $config; } /** * Set entity * * @param mixed $entity * @return $this * @codeCoverageIgnore */ public function setEntity($entity) { $this->_entity = $entity; return $this; } /** * Get entity * * @return mixed * @codeCoverageIgnore */ public function getEntity() { return $this->_entity; } public function getDeliveryDate() { $preValue = $this->getEntity()->getDeliveryDate(); if ($preValue && $preValue !== "0000-00-00 00:00:00") { return $preValue; } return null; } /** * @return array */ public function getConfigJson() { return json_encode($this->config->getConfig()); } }
Now as per highlighted code above, we will add our template file at app/code/Magelearn/DeliveryDate/view/frontend/templates/inline.phtml file.
<div id="delivery-date-<?= (int) $block->getEntity()->getId() ?>" data-bind="scope: 'delivery-date-<?= (int) $block->getEntity()->getId() ?>'"> <label class="label" data-bind="attr: { for: $t('Delivery Date') }"> <span data-bind="i18n: 'Delivery Date'"></span> </label> <!-- ko template: getTemplate() --><!-- /ko --> <script> var config = { settings: <?= $block->getConfigJson() ?>, preValue: "<?= $block->getDeliveryDate() ?>" }; if(!window.delivery_date){ window.delivery_date = [] } window.delivery_date['delivery-date-<?= (int) $block->getEntity()->getId() ?>'] = config; </script> <script type="text/x-magento-init"> { "*": { "Magento_Ui/js/core/app":{ "components": { "delivery-date-<?= (int) $block->getEntity()->getId() ?>": { "component": "Magelearn_DeliveryDate/js/view/multishipping-delivery-date", "template" : "Magelearn_DeliveryDate/multishipping-delivery-date", "entityId" : <?= (int) $block->getEntity()->getId() ?> } } } } } </script> </div>
As per highlighted code above, we will add our JS component file and template file.
Add file at app/code/Magelearn/DeliveryDate/view/frontend/web/js/view/multishipping-delivery-date.js file.
define([ 'jquery', 'ko', 'Magento_Ui/js/form/element/abstract', 'mage/calendar' ], function ($, ko, Component) { 'use strict'; return Component.extend({ initialize: function () { this._super(); ko.bindingHandlers.datepicker = { init: function (element, valueAccessor, allBindingsAccessor) { var $el = $(element); var parentId = $el.parent().attr("id"); var settings = window.delivery_date[parentId]; var config = settings.settings.shipping.delivery_date; var prevValue = settings.preValue; var defaultValue = config.default_delivery_date; var disabled = config.disabled; var blackout = config.blackout; var noday = config.noday; var hourMin = parseInt(config.hourMin); var hourMax = parseInt(config.hourMax); var format = config.format; if(!format) { format = 'yy-mm-dd'; } var disabledDay = disabled.split(",").map(function(item) { return parseInt(item, 10); }); //initialize datepicker var options = { minDate: 0, dateFormat:format, hourMin: hourMin, hourMax: hourMax, beforeShowDay: function(date) { if(blackout || noday){ var string = $.datepicker.formatDate('yy-mm-dd', date); var day = date.getDay(); var date_obj = []; 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); } } if(date_obj.indexOf(string) != -1 || disabledDay.indexOf(day) > -1) { return [false, arrayTooltipClass(blackout,string), arraySearch(blackout,string)]; } else { return [true]; } } } }; $el.datetimepicker(options); if(prevValue){ $el.datepicker("setDate", prevValue); } else if(defaultValue) { $el.datepicker("setDate", defaultValue); } var writable = valueAccessor(); if (!ko.isObservable(writable)) { var propWriters = allBindingsAccessor()._ko_property_writers; if (propWriters && propWriters.datepicker) { writable = propWriters.datepicker; } else { return; } } writable($(element).datetimepicker("getDate")); }, update: function (element, valueAccessor) { var widget = $(element).data("DatePicker"); //when the view model is updated, update the widget if (widget) { var date = ko.utils.unwrapObservable(valueAccessor()); widget.date(date); } } }; return this; }, getInputName: function () { return "delivery_date[" + this.entityId + "]"; }, getEntityId: function () { return this.entityId; } }); });Add template file at app/code/Magelearn/DeliveryDate/view/frontend/web/template/multishipping-delivery-date.html file.
<input class="input-text" type="text" data-bind=" event: {change: userChanges}, hasFocus: focused, value: value, datepicker: true, attr: { name: getInputName(), placeholder: placeholder, 'aria-describedby': noticeId, disabled: disabled }" data-validate="{required:true}" readonly="true"/>
Add app/code/Magelearn/DeliveryDate/Model/Type/Plugin/Multishipping.php file.
<?php namespace Magelearn\DeliveryDate\Model\Type\Plugin; class Multishipping { /** * @var \Magento\Framework\App\RequestInterface */ protected $request; /** * @var \Magelearn\DeliveryDate\Model\DeliveryDateManager */ private $delivery; public function __construct( \Magento\Framework\App\RequestInterface $request, \Magelearn\DeliveryDate\Model\DeliveryDateManager $delivery ) { $this->request = $request; $this->delivery = $delivery; } /** * Set shipping methods before event to capture the delivery dates * @param \Magento\Multishipping\Model\Checkout\Type\Multishipping $subject * @param $methods * @throws \Magento\Framework\Exception\LocalizedException */ public function beforeSetShippingMethods( \Magento\Multishipping\Model\Checkout\Type\Multishipping $subject, $methods ) { $deliveryDates = $this->request->getParam('delivery_date'); $quote = $subject->getQuote(); $this->delivery->add($deliveryDates, $quote); } }
Now as per highlighted code above, we will add app/code/Magelearn/DeliveryDate/Model/DeliveryDateManager.php file.
<?php namespace Magelearn\DeliveryDate\Model; class DeliveryDateManager { /** * @var Validator */ private $validator; public function __construct( Validator $validator ) { $this->validator = $validator; } /** * Add delivery dates values to quote address table * @param $deliveryDates * @param $quote * @return $this * @throws \Magento\Framework\Exception\LocalizedException */ public function add($deliveryDates, $quote) { if (!is_array($deliveryDates)) { return $this; } $addresses = $quote->getAllShippingAddresses(); foreach ($addresses as $address) { $addressId = $address->getId(); if (!isset($deliveryDates[$addressId])) { throw new \Magento\Framework\Exception\LocalizedException( __('Set empty delivery date for an address. Verify the delivery dates and try again.') ); } if (!$this->validator->validate($deliveryDates[$addressId]) ) { throw new \Magento\Framework\Exception\LocalizedException( __('Set not valid delivery date for an address. Verify the delivery dates and try again.') ); } $address->setDeliveryDate($deliveryDates[$addressId]); } return $this; } }
Now as per defined in etc/frontend/events.xml we will add our observer for checkout_type_multishipping_create_orders_single event.
Add app/code/Magelearn/DeliveryDate/Observer/MultishippingEventCreateOrdersObserver.php file.
<?php namespace Magelearn\DeliveryDate\Observer; use Magento\Framework\Event\ObserverInterface; class MultishippingEventCreateOrdersObserver implements ObserverInterface { /** * Set delivery date to order from address in multiple addresses checkout. * * @param \Magento\Framework\Event\Observer $observer * @return $this */ public function execute(\Magento\Framework\Event\Observer $observer) { $observer->getEvent()->getOrder()->setDeliveryDate($observer->getEvent()->getAddress()->getDeliveryDate()); return $this; } }
Now we will add our layout file to override default template file to display delivery date information on multishipping/checkout/overview page.
Add app/code/Magelearn/DeliveryDate/view/frontend/layout/multishipping_checkout_overview.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="checkout_overview"> <action method="setTemplate"> <argument name="template" xsi:type="string">Magelearn_DeliveryDate::checkout/overview.phtml</argument> </action> </referenceBlock> </body> </page>
As per highlighted code above we will add our template file. Copy file from vendor/magento/module-multishipping/view/frontend/templates/checkout/overview.phtml and add at view/frontend/templates/checkout/overview.phtml
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /** @var \Magento\Multishipping\Block\Checkout\Overview $block */ /** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */ ?> <?php /** @var \Magento\Tax\Helper\Data $taxHelper */ $taxHelper = $block->getData('taxHelper'); /** @var \Magento\Checkout\Helper\Data $checkoutHelper */ $checkoutHelper = $block->getData('checkoutHelper'); ?> <?php $errors = $block->getCheckoutData()->getAddressErrors(); ?> <?php foreach ($errors as $addressId => $error): ?> <div class="message message-error error"> <?= $block->escapeHtml($error); ?> <?= $block->escapeHtml(__('Please see')); ?> <a href="#<?= $block->escapeHtml($block->getCheckoutData()->getAddressAnchorName($addressId)); ?>"> <?= $block->escapeHtml(__('details below')); ?></a>. </div> <?php endforeach;?> <form action="<?= $block->escapeUrl($block->getPostActionUrl()); ?>" method="post" id="review-order-form" data-mage-init='{"orderOverview": {}, "validation":{}}' class="form multicheckout order-review"> <?= /* @noEscape */ $block->getBlockHtml('formkey'); ?> <div class="block block-billing"> <div class="block-title"><strong><?= $block->escapeHtml(__('Billing Information')); ?></strong></div> <div class="block-content"> <div class="box box-billing-address"> <?php $address = $block->getBillingAddress() ?> <strong class="box-title"> <span><?= $block->escapeHtml(__('Billing Address')); ?></span> <a href="<?= $block->escapeUrl($block->getEditBillingAddressUrl($address)); ?>" class="action edit"><span><?= $block->escapeHtml(__('Change')); ?></span></a> </strong> <div class="box-content"> <address> <?= /* @noEscape */ $address->format('html') ?> </address> </div> </div> <div class="box box-billing-method"> <strong class="box-title"> <span><?= $block->escapeHtml(__('Payment Method')); ?></span> <a href="<?= $block->escapeUrl($block->getEditBillingUrl()); ?>" class="action edit"><span><?= $block->escapeHtml(__('Change')); ?></span></a> </strong> <div class="box-content"> <input type="hidden" name="payment[cc_number]" value="<?= $block->escapeHtml($block->getPayment()->getCcNumber()) ?>" /> <input type="hidden" name="payment[cc_cid]" value="<?= $block->escapeHtml($block->getPayment()->getCcCid()) ?>" /> <?= /* @noEscape */ $block->getPaymentHtml() ?> </div> </div> </div> </div> <div class="block block-shipping"> <div class="block-title"><strong><?= $block->escapeHtml(__('Shipping Information')); ?></strong></div> <?php $mergedCells = ($taxHelper->displayCartBothPrices() ? 2 : 1); ?> <?php foreach ($block->getShippingAddresses() as $index => $address): ?> <div class="block-content"> <a name="<?= $block->escapeHtml($block->getCheckoutData() ->getAddressAnchorName($address->getId())); ?>"></a> <div class="title"> <strong><?= $block->escapeHtml(__('Address')); ?> <?= $block->escapeHtml($index + 1); ?> <span> <?= $block->escapeHtml(__('of')); ?> <?= $block->escapeHtml($block->getShippingAddressCount())?> </span> </strong> </div> <?php if ($error = $block->getCheckoutData()->getAddressError($address)): ?> <div class="error-description"><?= $block->escapeHtml($error); ?></div> <?php endif;?> <div class="box box-shipping-address"> <strong class="box-title"> <span><?= $block->escapeHtml(__('Shipping To')); ?></span> <a href="<?= $block->escapeUrl($block->getEditShippingAddressUrl($address)); ?>" class="action edit"><span><?= $block->escapeHtml(__('Change')); ?></span></a> </strong> <div class="box-content"> <address> <?= /* @noEscape */ $address->format('html') ?> </address> </div> </div> <div class="box box-shipping-method"> <strong class="box-title"> <span><?= $block->escapeHtml(__('Shipping Method')); ?></span> <a href="<?= $block->escapeUrl($block->getEditShippingUrl()); ?>" class="action edit"><span><?= $block->escapeHtml(__('Change')); ?></span></a> </strong> <?php if ($_rate = $block->getShippingAddressRate($address)): ?> <div class="box-content"> Delivery Date: <?= (new DateTime($address->getDeliveryDate()))->format('Y-m-d H:i:s') ?> <br/> <?= $block->escapeHtml($_rate->getCarrierTitle()) ?> (<?= $block->escapeHtml($_rate->getMethodTitle()) ?>) <?php $exclTax = $block->getShippingPriceExclTax($address); $inclTax = $block->getShippingPriceInclTax($address); $displayBothPrices = $taxHelper->displayShippingBothPrices() && $inclTax !== $exclTax; ?> <?php if ($displayBothPrices): ?> <span class="price-including-tax" data-label="<?= $block->escapeHtml(__('Incl. Tax')); ?>"> <?= /* @noEscape */ $inclTax ?> </span> <span class="price-excluding-tax" data-label="<?= $block->escapeHtml(__('Excl. Tax')); ?>"> <?= /* @noEscape */ $exclTax; ?> </span> <?php else: ?> <?= /* @noEscape */ $inclTax ?> <?php endif; ?> </div> <?php endif; ?> </div> <div class="box box-items"> <div class="box-content"> <div class="order-review-wrapper table-wrapper"> <table class="items data table table-order-review" id="overview-table-<?= $block->escapeHtml($address->getId()); ?>"> <caption class="table-caption"><?= $block->escapeHtml(__('Order Review')); ?></caption> <thead> <tr> <th class="col item" scope="col"><?= $block->escapeHtml(__('Item')); ?> <a href="<?= $block->escapeUrl($block->getAddressesEditUrl()); ?>" class="action edit"> <span><?= $block->escapeHtml(__('Edit')); ?></span> </a> </th> <th class="col price" scope="col"><?= $block->escapeHtml(__('Price')); ?></th> <th class="col qty" scope="col"><?= $block->escapeHtml(__('Qty')); ?></th> <th class="col subtotal" scope="col"><?= $block->escapeHtml(__('Subtotal')); ?></th> </tr> </thead> <tbody> <?php foreach ($block->getShippingAddressItems($address) as $item): ?> <?= /* @noEscape */ $block->getRowItemHtml($item) ?> <?php endforeach; ?> </tbody> <tfoot> <?= /* @noEscape */ $block->renderTotals( $block->getShippingAddressTotals($address) ); ?> </tfoot> </table> </div> </div> </div> </div> <?php endforeach; ?> </div> <?php if ($block->getQuote()->hasVirtualItems()): ?> <div class="block block-other"> <?php $billingAddress = $block->getQuote()->getBillingAddress(); ?> <a name="<?= $block->escapeHtml($block->getCheckoutData() ->getAddressAnchorName($billingAddress->getId())); ?>"></a> <div class="block-title"><strong><?= $block->escapeHtml(__('Other items in your order')); ?></strong></div> <?php if ($error = $block->getCheckoutData()->getAddressError($billingAddress)): ?> <div class="error-description"><?= $block->escapeHtml($error); ?></div> <?php endif;?> <div class="block-content"> <strong class="subtitle"> <span><?= $block->escapeHtml(__('Items')); ?></span> <a href="<?= $block->escapeUrl($block->getVirtualProductEditUrl()); ?>" class="action edit"><span><?= $block->escapeHtml(__('Edit Items')); ?></span></a> </strong> <?php $mergedCells = ($taxHelper->displayCartBothPrices() ? 2 : 1); ?> <div class="order-review-wrapper table-wrapper"> <table class="items data table table-order-review" id="virtual-overview-table"> <caption class="table-caption"><?= $block->escapeHtml(__('Items')); ?></caption> <thead> <tr> <th class="col item" scope="col"><?= $block->escapeHtml(__('Product Name')); ?></th> <th class="col price" scope="col"><?= $block->escapeHtml(__('Price')); ?></th> <th class="col qty" scope="col"><?= $block->escapeHtml(__('Qty')); ?></th> <th class="col subtotal" scope="col"><?= $block->escapeHtml(__('Subtotal')); ?></th> </tr> </thead> <tbody> <?php foreach ($block->getVirtualItems() as $_item): ?> <?= /* @noEscape */ $block->getRowItemHtml($_item) ?> <?php endforeach; ?> </tbody> <tfoot> <?= /* @noEscape */ $block->renderTotals($block->getBillingAddressTotals()); ?> </tfoot> </table> </div> </div> </div> <?php endif; ?> <?= /* @noEscape */ $block->getChildHtml('items_after') ?> <div id="checkout-review-submit" class="checkout-review"> <?= /* @noEscape */ $block->getChildHtml('agreements') ?> <div class="grand totals"> <strong class="mark"><?= $block->escapeHtml(__('Grand Total:')); ?></strong> <strong class="amount"> <?= /* @noEscape */ $checkoutHelper->formatPrice($block->getTotal()); ?> </strong> </div> <div class="actions-toolbar" id="review-buttons-container"> <div class="primary"> <?= $block->getChildHtml('captcha') ?> <button type="submit" class="action primary submit" id="review-button"><span><?= $block->escapeHtml(__('Place Order')); ?></span> </button> </div> <div class="secondary"> <a href="<?= $block->escapeUrl($block->getBackUrl()); ?>" class="action back"> <span><?= $block->escapeHtml(__('Back to Billing Information')); ?></span> </a> </div> <span id="review-please-wait" class="please-wait load indicator" data-text="<?= $block->escapeHtml(__('Submitting order information...')); ?>"> <span><?= $block->escapeHtml(__('Submitting order information...')); ?></span> </span> <?= /* @noEscape */ $secureRenderer->renderStyleAsTag('display: none;', 'span#review-please-wait') ?> </div> </div> </form>
we will also add app/code/Magelearn/DeliveryDate/view/adminhtml/ui_component/sales_order_grid.xml file to add this column in sales >> order grid.
<?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="delivery_date"> <argument name="data" xsi:type="array"> <item name="js_config" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/column</item> </item> <item name="config" xsi:type="array"> <item name="visible" xsi:type="boolean">true</item> <item name="dataType" xsi:type="string">text</item> <item name="align" xsi:type="string">left</item> <item name="label" xsi:type="string" translate="true">Delivery Date</item> </item> </argument> </column> </columns> </listing>
And at last we will add our CSS file.
As per defined in view/adminhtml/layout/sales_order_create_index.xml file we will add our CSS file for admin at view/adminhtml/web/css/datetimepicker.css and at view/frontend/web/css/source/_module.less to display different color for blackout days and normal disabled day.
With this file, Magento will generate and compile CSS to style-l and style-m accordingly with compilation command.
0 Comments On "Add Order Delivery Date and Order Comment in Checkout Page Magento2"