Magento – “Cannot save shipment.” error with non-sample orders. 1062 Duplicate entry * for key ‘UNQ_SALES_FLAT_SHIPMENT_INCREMENT_ID’

order-statusorderssales-ordersample-datashipment

I'm running test orders on my almost ready Magento store but faced a very strange problem: I cannot save shipments in admin.

So there are some test orders I made myself as a guest and a signed-in customer. The payment process goes fine through. When I'm on the admin side I can invoice the orders normally, but when I select Ship > Submit Shipment I get this error "Cannot save shipment.".

However, if I scroll down to the orders provided in the Magento sample data, there are some orders that are in the state of Processing – when I follow the aforementioned procedure to ship those, the shippings are created normally and everything works fine.

Same problem occurs regardless of Payment Method or Shipping Method used. However, the sample data orders use Payment & Shipping Methods that are already disabled in the front-end, but that hasn't got any effect: the shipment creation succeeds for sample orders.

What on Earth causes this kind of behaviour? What can I do? Can there be something wrong with the products or Shipping Methods? Pls help me out.

Update 1:

I now edited the function canShip() at app/code/core/Mage/Sales/Model/Order.php

public function canShip()
{
    return true;/*FOR DEBUGGING PURPOSES*/

    /*
    if ($this->canUnhold() || $this->isPaymentReview()) {
        return false;
    }

    if ($this->getIsVirtual() || $this->isCanceled()) {
        return false;
    }

    if ($this->getActionFlag(self::ACTION_FLAG_SHIP) === false) {
        return false;
    }

    foreach ($this->getAllItems() as $item) {
        if ($item->getQtyToShip()>0 && !$item->getIsVirtual()
            && !$item->getLockedDoShip())
        {
            return true;
        }
    }
    return false;
    */
}

But still I got the same results. I flushed Magento cache after making the changes. Then I also tried to set the function to return: false; for every run but still I was able to ship the older orders (magento sample data's pending/processing orders).

Minor Update to above: So now it appears that canShip() function returns true because when I set it to return false; for everything, the orders are just passed through to complete state without the need to Ship them in Admin. I didn't flush caches etc. properly enough yesterday.

UPDATE 2:

I managed to track down the problem a bit further:
On app/code/core/Mage/Adminhtml/controllers/Sales/Order/ShipmentController.php line 238 an Exception gets caught (catch (Exception $e)), which results in the error msg "Cannot save shipment" on admin panel.

This is what the function tries to do:

public function saveAction()
{
    $data = $this->getRequest()->getPost('shipment');
    if (!empty($data['comment_text'])) {
        Mage::getSingleton('adminhtml/session')->setCommentText($data['comment_text']);
    }

    try {
        $shipment = $this->_initShipment();
        if (!$shipment) {
            $this->_forward('noRoute');
            return;
        }

        $shipment->register();
        $comment = '';
        if (!empty($data['comment_text'])) {
            $shipment->addComment(
                $data['comment_text'],
                isset($data['comment_customer_notify']),
                isset($data['is_visible_on_front'])
            );
            if (isset($data['comment_customer_notify'])) {
                $comment = $data['comment_text'];
            }
        }

        if (!empty($data['send_email'])) {
            $shipment->setEmailSent(true);
        }

        $shipment->getOrder()->setCustomerNoteNotify(!empty($data['send_email']));
        $responseAjax = new Varien_Object();
        $isNeedCreateLabel = isset($data['create_shipping_label']) && $data['create_shipping_label'];

        if ($isNeedCreateLabel && $this->_createShippingLabel($shipment)) {
            $responseAjax->setOk(true);
        }

        $this->_saveShipment($shipment);

        $shipment->sendEmail(!empty($data['send_email']), $comment);

        $shipmentCreatedMessage = $this->__('The shipment has been created.');
        $labelCreatedMessage    = $this->__('The shipping label has been created.');

        $this->_getSession()->addSuccess($isNeedCreateLabel ? $shipmentCreatedMessage . ' ' . $labelCreatedMessage
            : $shipmentCreatedMessage);
        Mage::getSingleton('adminhtml/session')->getCommentText(true);
    } [...etc...]

Then comes the catch part of the function.

Exception log:

exception 'PDOException' with message 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '400000001' for key 'UNQ_SALES_FLAT_SHIPMENT_INCREMENT_ID'' in [my shop path]/lib/Zend/Db/Statement/Pdo.php:228

Next exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '400000001' for key 'UNQ_SALES_FLAT_SHIPMENT_INCREMENT_ID'' in [my shop path]/lib/Zend/Db/Statement/Pdo.php:234

(Note: "[my shop path]" is added by me, it's not quoted from the exception message.)

UPDATE 4: FOUND THE SOLUTION

After googling the exception log messages and error codes, I found an issue on StackOverflow which was pretty similar. I write it down here since it's not 100 % the same in my case.

  1. Admin > Sales > Orders: Look up the highest order number (for each store view) and write it down. e.g. 100000054, which means that there are 54 placed orders in the system.
  2. Admin > System > Manage Stores: Hover your mouse over your Store View Names to see their ID:s. Write them down. e.g. ID for Store View English is usually 1.
  3. Open your database in phpMyAdmin or your preferred db admin tool.
  4. Open the table eav_entity_type which tells you what is the id number for different entities. Now we are interested in rows with entity_type_code is order and shipment (and maybe also invoice and creditmemo, if your problem is related to those. My problem was about shipping). Write down the entity_type_id values of those rows. In my case they were 5for order and 8for shipment.
  5. Open the table eav_entity_store. Look for rows that match the entity_type_ids of order and shipment AND also match your Store View ID:s. Now you can change the value of increment_last_id to your last actual order number. Since my store isn't open yet, I played it safe and gave a value much bigger for both order and shipment related rows. e.g. if the increment_last_ids were 100000053 for orders and 100000040 for shipments, I gave them both the same new value 100000100 to start over from a clean table. N.B. In my case there was no a row with my local store view and correct shipment entity_type_id. So I copied the row of store view id 1, gave it new values (store id -> 4 and also increment_prefix to match store_id -> 4, and of course increment_last_id -> 400000100) and saved it as a new row in the table.

And that's it. I've now placed a few test orders and everything seems to work. I was also able to ship some older test orders that were lagging behind in Processing mode. That resulted in the fact that order ID:s and shipment ID:s aren't running side-by-side, but they wouldn't do that for long anyway so it's not a problem.

Reflecion:

If I understood this correctly, I think my problem was that for shipment my increment_last_id was too small and the system tried to create a new shipment with an ID that already existed in the database.

The reason why I was able to ship the magento sample data's orders was that they had been done in English Store View, which apparently wasn't corrupted. I'm not sure how my native Store View first ran into the problem – what made the increment_last_id drop back. My guess is that since I'm running 4 different environments for testing Magento, there has happened something that I've different amounts of test purchases in different environments and there has occurred some kind of a version control issue. Dunno 'bout that.

Best Answer

Anssi,Magento shipment depends on canShip function of Order.php(Mage_Sales_Model_Order)

 public function canShip()
    {
        if ($this->canUnhold() || $this->isPaymentReview()) {
            return false;
        }

        if ($this->getIsVirtual() || $this->isCanceled()) {
            return false;
        }

        if ($this->getActionFlag(self::ACTION_FLAG_SHIP) === false) {
            return false;
        }

        foreach ($this->getAllItems() as $item) {
            if ($item->getQtyToShip()>0 && !$item->getIsVirtual()
                && !$item->getLockedDoShip())
            {
                return true;
            }
        }
        return false;
    }

Explanation:

Condition1: if ($this->canUnhold() || $this->isPaymentReview()) means if Order state code is unhold or order state code is payment_review you cannot create shipment.

Condition2: if ($this->isCanceled() || $this->getIsVirtual() ) {

If order state is cancel or order is virtual means ,you have buy a virtual product,you can not do a shipment

Condition 3:

 if ($this->getActionFlag(self::ACTION_FLAG_SHIP) === false) {
If current action is ship then you can create Shipment

Condition4:

if($item->getQtyToShip()>0 && !$item->getIsVirtual()
                && !$item->getLockedDoShip())

if order all item qty_to_ship value is greater than 0 and Order is not virtual and all items column locked_do_ship is null (In sales_flat_order_item table)

Related Topic