How to Cancel an Order Item in Magento 2?

How to Cancel an Order Item in Magento 2?

In Magento 2 we have the option to Cancel an Order but there is no way in the default Magento 2 setup to cancel an order item in Magento 2, so we will discuss this in the following topic How to Cancel an Order Item in Magento 2.

Step 1

Firstly we have to override the sales_order_view.xml file and add it to our custom module. The location for this file will be Vendor/Module/view/adminhtml/layout/sales_order_view.xml.

This we are doing to override the Sales Order View Layout and add our changes to Order item cancellation.

<?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">
    <update handle="sales_order_transactions_grid_block"/>
    <head>
        <link src="Magento_Sales::js/bootstrap/order-create-index.js"/>
        <link src="Magento_Sales::js/bootstrap/order-post-action.js"/>
    </head>
    <update handle="sales_order_item_price"/>
    <body>
        <referenceContainer name="admin.scope.col.wrap" htmlClass="admin__old" />
        <referenceContainer name="content">
            <block class="Magento\Sales\Block\Adminhtml\Order\View" name="sales_order_edit"/>
        </referenceContainer>
        <referenceContainer name="left">
            <block class="Magento\Sales\Block\Adminhtml\Order\View\Tabs" name="sales_order_tabs">
                <block class="Magento\Sales\Block\Adminhtml\Order\View\Tab\Info" name="order_tab_info" template="Magento_Sales::order/view/tab/info.phtml">
                    <block class="Magento\Sales\Block\Adminhtml\Order\View\Messages" name="order_messages"/>
                    <block class="Magento\Sales\Block\Adminhtml\Order\View\Info" name="order_info" template="Magento_Sales::order/view/info.phtml">
                        <container name="extra_customer_info"/>
                    </block>
                    <container name="order_additional_info"/>
                        <block class="Magento\Sales\Block\Adminhtml\Order\View\Items" name="order_items" template="Vendor_Module::order/view/items.phtml">
                            <arguments>
                                <argument name="columns" xsi:type="array">
                                    <item name="product" xsi:type="string" translate="true">Product</item>
                                    <item name="status" xsi:type="string" translate="true">Item Status</item>
                                    <item name="price-original" xsi:type="string" translate="true">Original Price</item>
                                    <item name="price" xsi:type="string" translate="true">Price</item>
                                    <item name="ordered-qty" xsi:type="string" translate="true">Qty</item>
                                    <item name="subtotal" xsi:type="string" translate="true">Subtotal</item>
                                    <item name="tax-amount" xsi:type="string" translate="true">Tax Amount</item>
                                    <item name="tax-percent" xsi:type="string" translate="true">Tax Percent</item>
                                    <item name="discont" xsi:type="string" translate="true">Discount Amount</item>
                                    <item name="total" xsi:type="string" translate="true">Row Total</item>
                                </argument>
                           </arguments>
                       <block class="Magento\Sales\Block\Adminhtml\Order\View\Items\Renderer\DefaultRenderer" as="default" name="default_order_items_renderer" template="Vendor_Module::order/view/items/renderer/default.phtml">
                           <arguments>
                               <argument name="columns" xsi:type="array">
                                   <item name="product" xsi:type="string" translate="false">col-product</item>
                                   <item name="status" xsi:type="string" translate="false">col-status</item>
                                   <item name="price-original" xsi:type="string" translate="false">col-price-original</item>
                                   <item name="price" xsi:type="string" translate="false">col-price</item>
                                   <item name="qty" xsi:type="string" translate="false">col-ordered-qty</item>
                                   <item name="subtotal" xsi:type="string" translate="false">col-subtotal</item>
                                   <item name="tax-amount" xsi:type="string" translate="false">col-tax-amount</item>
                                   <item name="tax-percent" xsi:type="string" translate="false">col-tax-percent</item>
                                   <item name="discont" xsi:type="string" translate="false">col-discont</item>
                                   <item name="total" xsi:type="string" translate="false">col-total</item>
                               </argument>
                           </arguments>
                       </block>
                       <block class="Magento\Sales\Block\Adminhtml\Items\Column\Qty" name="column_qty" template="Magento_Sales::items/column/qty.phtml" group="column"/>
                       <block class="Magento\Sales\Block\Adminhtml\Items\Column\Name" name="column_name" template="Magento_Sales::items/column/name.phtml" group="column"/>
                       <block class="Magento\Framework\View\Element\Text\ListText" name="order_item_extra_info"/>
                   </block>
                   <container name="payment_additional_info" htmlTag="div" htmlClass="order-payment-additional" />
                   <block class="Magento\Sales\Block\Adminhtml\Order\Payment" name="order_payment"/>
                   <block class="Magento\Sales\Block\Adminhtml\Order\View\History" name="order_history" template="Magento_Sales::order/view/history.phtml"/>
                   <block class="Magento\Backend\Block\Template" name="gift_options" template="Magento_Sales::order/giftoptions.phtml">
                       <block class="Magento\Sales\Block\Adminhtml\Order\View\Giftmessage" name="order_giftmessage" template="Magento_Sales::order/view/giftmessage.phtml"/>
                   </block>
                   <block class="Magento\Sales\Block\Adminhtml\Order\Totals" name="order_totals" template="Magento_Sales::order/totals.phtml">
                       <block class="Magento\Sales\Block\Adminhtml\Order\Totals\Tax" name="tax" template="Magento_Sales::order/totals/tax.phtml"/>
                   </block>
               </block>
               <action method="addTab">
                   <argument name="name" xsi:type="string">order_info</argument>
                   <argument name="block" xsi:type="string">order_tab_info</argument>
               </action>
               <block class="Magento\Sales\Block\Adminhtml\Order\View\Tab\Invoices" name="sales_order_invoice.grid.container"/>
               <action method="addTab">
                   <argument name="name" xsi:type="string">order_invoices</argument>
                   <argument name="block" xsi:type="string">sales_order_invoice.grid.container</argument>
               </action>
               <block class="Magento\Sales\Block\Adminhtml\Order\View\Tab\Creditmemos" name="sales_order_creditmemo.grid.container"/>
               <action method="addTab">
                   <argument name="name" xsi:type="string">order_creditmemos</argument>
                   <argument name="block" xsi:type="string">sales_order_creditmemo.grid.container</argument>
               </action>
               <block class="Magento\Sales\Block\Adminhtml\Order\View\Tab\Shipments" name="sales_order_shipment.grid.container"/>
               <action method="addTab">
                   <argument name="name" xsi:type="string">order_shipments</argument>
                   <argument name="block" xsi:type="string">sales_order_shipment.grid.container</argument>
               </action>
               <action method="addTab">
                   <argument name="name" xsi:type="string">order_history</argument>
                   <argument name="block" xsi:type="string">Magento\Sales\Block\Adminhtml\Order\View\Tab\History</argument>
               </action>
               <block class="Magento\Sales\Block\Adminhtml\Order\View\Tab\Transactions" name="sales_transactions.grid.container"/>
               <action method="addTab">
                   <argument name="name" xsi:type="string">order_transactions</argument>
                   <argument name="block" xsi:type="string">sales_transactions.grid.container</argument>
               </action>
            </block>
        </referenceContainer>
        <referenceBlock name="head.components">
            <block class="Magento\Framework\View\Element\Js\Components" name="sales_page_head_components" template="Magento_Sales::page/js/components.phtml"/>
        </referenceBlock>
        <referenceBlock name="sales_order_invoice.grid.container">
            <uiComponent name="sales_order_view_invoice_grid"/>
        </referenceBlock>
        <referenceBlock name="sales_order_creditmemo.grid.container">
            <uiComponent name="sales_order_view_creditmemo_grid"/>
        </referenceBlock>
        <referenceBlock name="sales_order_shipment.grid.container">
            <uiComponent name="sales_order_view_shipment_grid"/>
        </referenceBlock>
    </body>
</page>

Step 2

Now, this step will create a route to process our controller action. Now create a file named items.phtml in path Vendor/Module/view/adminhtml/templates/order/view

<?php
$_order = $block->getOrder() ?>
<div class="admin__table-wrapper">
    <table class="data-table admin__table-primary edit-order-table">
        <thead>
            <tr class="headings">
                <?php 
                    $i = 0;
                    $columns = $block->getColumns();
                    $lastItemNumber = count($columns);
                ?>
                <?php foreach ($columns as $columnName => $columnTitle): ?>
                    <?php $i++; ?>
                    <th class="col-<?= $block->escapeHtmlAttr($columnName) ?><?= ($i === $lastItemNumber ? ' last' : '') ?>"><span><?= $block->escapeHtml($columnTitle) ?></span></th>
                <?php endforeach; ?>
                <th class="col select-qty"><?= $block->escapeHtml(__('Select Qty')) ?></th>
                <th class="col action"><?= $block->escapeHtml(__('Action')) ?></th>
            </tr>
        </thead>
        <?php $_items = $block->getItemsCollection();?>
        <?php $i = 0; foreach ($_items as $_item) : ?>
        <?php if ($_item->getParentItem()) :
            continue;
         else :
             $i++;
         endif; ?>
         <tbody class="<?= $i%2 ? 'even' : 'odd' ?>">
             <?= $block->getItemHtml($_item) ?>
             <?= $block->getItemExtraInfoHtml($_item) ?>
         </tbody>
        <?php endforeach; ?>
    </table>
</div>

Step 3

Now, this step will create a route to process our controller action. Now create a file named default.phtml in path Vendor/Module/view/adminhtml/templates/order/view/items/renderer.

<?php
    $_item = $block->getItem();
    $itemId = $_item->getItemId();
    $orderId = $this->getRequest()->getParam('order_id');
    $allowdQty = $_item->getQtyOrdered() - $_item->getQtyShipped() - $_item->getQtyCanceled();
?>
<?php $block->setPriceDataObject($_item) ?>
<tr>
<?php $i = 0;
$columns = $block->getColumns();
$lastItemNumber = count($columns) ?>
<?php foreach ($columns as $columnName => $columnClass) : ?>
    <?php $i++; ?>
    <td class="<?= $columnClass ?><?= ($i === $lastItemNumber ? ' last' : '') ?>">
        <?= $block->getColumnHtml($_item, $columnName) ?>
    </td>
<?php endforeach; ?>

<td data-th="<?= $block->escapeHtml(__('Select-Qty')) ?>" class="col select-qty">
<?php if($allowdQty != 0): ?>
<select name="item_qty" data-id="<?= $itemId ?>" class="customCancel" id="selectItemQty-<?= $itemId ?>">
<option value="">Select Qty</option>
<?php for($i = 1; $i <= $allowdQty; $i++): ?>
<option value="<?php echo $this->getUrl('cancelorderitem/item/cancel').'?orderid='. $orderId .'&itemid='. $itemId .'&qty='.$i; ?>"><?= $i ?></option>
<?php endfor; ?>
</select>
<?php endif; ?>
<p style="display: none; color: red;" class='errormsg-<?= $itemId ?>'> Required*</p>
</td>

<td data-th="<?= $block->escapeHtml(__('Actions')) ?>" class="col actions">
<?php if($allowdQty != 0): ?>
<a href="javascript:void(0);" id="cancelbtn-<?= $itemId ?>" class="action cancelbtn cancel"><?= /* @escapeNotVerified */ __('Cancel') ?></a>
<?php endif; ?>
</td>
</tr>

<script type="text/javascript">
    require(['jquery'], function($) {
        $('#selectItemQty-<?= $itemId ?>').change(function(){
            url = $(this).val();
            var nextel = $(this).closest('td').next('td').find('.cancelbtn');
            nextel.attr('href',url);
        });

        $('#cancelbtn-<?= $itemId ?>').click(function(){
            var url = $(this).attr('href');
            if(url != 'javascript:void(0);') {
                $(this).attr('href', demo);
            } else {
                $('.errormsg-<?= $itemId ?>').show()
                setTimeout(() => {
                    $('.errormsg-<?= $itemId ?>').fadeOut()
                }, 1000);
            }
        });
    });
</script>

Step 4

This step will create a route to process our controller action. Now create file routes.xml in path Vendor/Module/etc/adminhtml.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="cancelorderitem" frontName="cancelorderitem">
            <module name="Vendor_Module"/>
        </route>
    </router>
</config>

Step 5

Now finally we will create our Controller to cancel the ordered item. We will create a file Cancel.php in Vendor/Module/Controller/Adminhtml/Item.

<?php

namespace Vendor\Module\Controller\Adminhtml\Item;

use Magento\Backend\App\Action;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\Registry;
use Magento\Sales\Model\Order\ItemFactory;
use Magento\Sales\Model\OrderFactory;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\Event\ManagerInterface;
use Magento\Sales\Model\Order\Item;
use Magento\Setup\Exception;

class Cancel extends \Magento\Backend\App\Action
{
protected $_coreRegistry = null;
protected $resultPageFactory;
protected $_itemFactory;
protected $_orderFactory;
protected $_orderRepository;
protected $_eventManager;
protected $_items;

/**
* Item constructor.
* @param Context $context
* @param ItemFactory $itemFactory
* @param OrderFactory $orderFactory
* @param OrderRepositoryInterface $orderRepository
*/
public function __construct(
Action\Context $context,
PageFactory $resultPageFactory,
Registry $registry,
ItemFactory $itemFactory,
OrderFactory $orderFactory,
OrderRepositoryInterface $orderRepository,
ManagerInterface $eventManager,
Item $items
) {
$this->resultPageFactory = $resultPageFactory;
$this->_coreRegistry = $registry;
parent::__construct($context);
$this->_itemFactory = $itemFactory;
$this->_orderFactory = $orderFactory;
$this->_orderRepository = $orderRepository;
$this->_eventManager = $eventManager;
$this->_items = $items;
}

public function execute()
{
$id = $this->getRequest()->getParam('itemid');
$orderId = $this->getRequest()->getParam('orderid');
$qty = $this->getRequest()->getParam('qty');
$bagId = $this->getRequest()->getParam('bagid');

try {
$item = $this->_itemFactory->create();
$item->load($id);

$order = $this->_orderFactory->create();
$order->load($orderId);
$itemsOrdered = $order->getTotalItemCount();

$allowedCancel = ($item->getQtyOrdered() - $item->getQtyShipped()) - $item->getQtyCanceled();

$qtyToBeCancelled = $qty + $item->getQtyCanceled();
$canceledItem = $item->getName();
$cancelOrder = true;

foreach ($order->getAllVisibleItems() as $orderItem) {
if ($orderItem->getItemId() == $id) {
$orderItem->setQtyCanceled($qtyToBeCancelled)->save();
}

if ($orderItem->getQtyOrdered() != $orderItem->getQtyCanceled()) {
$cancelOrder = false;
}
}

if ($itemsOrdered == 1 && ($item->getQtyOrdered() <= $item->getQtyCanceled())) {
$cancelOrder = true;
}

$this->messageManager->addSuccess(__('You have cancelled item ' . $canceledItem));

if ((int) $cancelOrder) {
$this->cancel($order);
}

} catch (\Exception $e) {
$this->messageManager->addException($e, $e->getMessage());
}

$resultRedirect = $this->resultRedirectFactory->create();
$resultRedirect->setUrl($this->_redirect->getRefererUrl());
return $resultRedirect;
}

protected function cancel($order) {
$state = \Magento\Sales\Model\Order::STATE_CANCELED;
$order->setSubtotalCanceled($order->getSubtotal() - $order->getSubtotalInvoiced());
$order->setBaseSubtotalCanceled($order->getBaseSubtotal() - $order->getBaseSubtotalInvoiced());

$order->setTaxCanceled($order->getTaxAmount() - $order->getTaxInvoiced());
$order->setBaseTaxCanceled($order->getBaseTaxAmount() - $order->getBaseTaxInvoiced());

$order->setShippingCanceled($order->getShippingAmount() - $order->getShippingInvoiced());
$order->setBaseShippingCanceled($order->getBaseShippingAmount() - $order->getBaseShippingInvoiced());

$order->setDiscountCanceled(abs($order->getDiscountAmount()) - $order->getDiscountInvoiced());
$order->setBaseDiscountCanceled(abs($order->getBaseDiscountAmount()) - $order->getBaseDiscountInvoiced());

$order->setTotalCanceled($order->getGrandTotal() - $order->getTotalPaid());
$order->setBaseTotalCanceled($order->getBaseGrandTotal() - $order->getBaseTotalPaid());

$order->setState($state)
->setStatus($state);

$this->_orderRepository->save($order);
}
}

Note: Keep in mind that you can only cancel an order item if the order has not yet been shipped. If the order has already been shipped, you will need to contact the customer and ask them to return the item for a refund.

Download the full extension from here:

https://github.com/razecodetech/magento2-cancel-order-item

OR

https://razecode.com/magento-2-cancel-order-item/

So this was the brief workaround to discuss the topic of Magento 2 cancel order items.

I hope this helps! If you have any further questions, please don’t hesitate to reach out.

Must Read: https://techurbane.com/must-have-magento-2-extensions-supercharge-your-store/

Also, Check for Magento 2 Order Cancellation Module from Amasty with some more features

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments