2

Magento won't let you create a credit memo for $0. In the case of a $0 order, what is the correct way to reverse/close/cancel the order, and return the item to stock?

From looking at the core code, it looks like Magento has some reason for not allowing credit-memos for $0 orders, but I can't understand what it is (although that isn't what this question is about.)

The order's canCreditmemo() method returns false if the total minus the refunded is zero (which it always will be for a $0 order).

if (abs($this->getStore()->roundPrice($this->getTotalPaid()) - $this->getTotalRefunded()) < .0001) {
    return false;
}

In the credit memo controller, it requires that you enter a positive number, but for a $0 order, there is no actual money to refund or credit.

if (($creditmemo->getGrandTotal() <=0) && (!$creditmemo->getAllowZeroGrandTotal())) {
    Mage::throwException(
        $this->__('Credit memo\'s total must be positive.')
    );
}

You are probably wondering why on earth this is a use-case:

Our client was creating a manual order through the admin for someone. The order total was (intentionally) $0. Unfortunately, they "purchased" the wrong product, which throws off their inventory and their reports. We ended up setting the order to "Canceled" status manually by calling $order->setStatus('Canceled');, but to me this isn't a very ideal solution because it doesn't return the item to stock, nor does it save any history of what happened, such that a future user could understand that the product was/wasn't returned to stock.

As a Magento developer, what would you advise your client to do in this scenario?

Since this is Magento stack-exchange, "Use a real ERP and inventory management system" is not an answer.

2

I ran into the same problem but I approached the solution a bit differently using di preferences; here is the code:

My/Module/etc/adminhtml/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Sales\Controller\Adminhtml\Order\CreditmemoLoader" type="My\Module\Controller\Adminhtml\Order\CreditmemoLoader"/>
</config>

My/Module/Controller/Adminhtml/Order/CreditmemoLoader.php

<?php

namespace My\Module\Controller\Adminhtml\Order;

class CreditmemoLoader extends \Magento\Sales\Controller\Adminhtml\Order\CreditmemoLoader {

    protected function _canCreditmemo($order)
    {
        if(!$order->isCanceled() && $order->getState() !== \Magento\Sales\Model\Order::STATE_CLOSED)
            $order->setForcedCanCreditmemo(true);

        return parent::_canCreditmemo($order);
    }

    public function load()
    {
        $result = parent::load();
        if($result instanceof \Magento\Sales\Model\Order\Creditmemo){
            $result->setAllowZeroGrandTotal(true);
        }
        return $result;
    }

}

Here I'm using preferences (we can't use plugins because of the protected function) to override the CreditmemoLoader class which is used to create CMs. I use setForcedCanCreditmemo to make sure we can create the CM on the Order and setAllowZeroGrandTotal to allow a $0 CM. You could perform additional checks on the order here too.

0

CM's grand total restriction is verrified only by Magento core controller. If you override CM submit url to point to your controller you can bypass that check. You can also do it somewhere in your module programatically:

$data = array(
    'qtys' => $qtysToRefund,
    'items' => $itemsToRefund,
    'shipping_amount' => 0,
    'base_shipping_amount' => 0,
    'shipping_tax_amount' => 0,
    'base_shipping_tax_amount' => 0,
    'shipping_hidden_tax_amount' => 0,
    'base_shipping_hidden_tax_amnt' => 0,
    'shipping_incl_tax' => 0,
    'base_shipping_incl_tax' => 0,
    ...
);

// following model may require some overrides
$service = Mage::getModel('your_module/sales_service_order', $order);
$creditmemo = $service->prepareCreditmemo($data);
$creditmemo->setAllowZeroGrandTotal(true); // just in case, but don't think it is really needed
$creditmemo->addComment('Your CM comment.');

// see core CreditmemoController.php @ 144 how it does the following
foreach ($creditmemo->getAllItems() as $creditmemoItem) {
    $creditmemoItem->setBackToStock(true);
}

$creditmemo->register();
$cm_transaction = Mage::getModel('core/resource_transaction')
    ->addObject($creditmemo)
    ->addObject($creditmemo->getOrder())
;
$cm_transaction->save();

Not sure if this is the case (took those snippets from one of old projects), but you may have to override _initCreditmemoData method from Mage_Sales_Model_Service_Order.

This is not a complete solution, it just shows how this may be achieved.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.