Solved! Avoid creating multiple orders for a single cart in Prestashop

In Prestashop we occasionally encounter an annoying problem. The customer orders got divided into more than one single order, keeping the same reference code.

PedidosDuplicados

Following a thorough investigation we realized that the problem had a name: Carriers. Specifically, the type of carrier associated with products that compose the cart.

ConfiguracionTransportistaBy default Prestashop separates the items from the cart in different orders grouped by its associated carrier. In theory it’s a good system to assist us while internally managing the shipping of goods.

The problem arises when, as in our case, we only use a carrier but generate another one called free carrier for the items you want to sell without the tax of a carrier, as in the case of gifts.

Transportistas

This is one of many cases in which Prestashop team decide a behavior without giving the option to disable it or adjust it.

The only way to achieve this is to modify this functionality via class overrides. Specifically the class Cart.php and inside it the function getPackageList(). The highlighted lines are those that have been added to the original schedule.

	public function getPackageList($flush = false)
	{
		static $cache = array();

		if (isset($cache[(int)$this->id.'_'.(int)$this->id_address_delivery]) && $cache[(int)$this->id.'_'.(int)$this->id_address_delivery] !== false && !$flush)
			return $cache[(int)$this->id.'_'.(int)$this->id_address_delivery];

		$product_list = $this->getProducts();

		// Step 1 : Get product informations (warehouse_list and carrier_list), count warehouse
		// Determine the best warehouse to determine the packages
		// For that we count the number of time we can use a warehouse for a specific delivery address
		$warehouse_count_by_address = array();
		$warehouse_carrier_list = array();

		$stock_management_active = Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT');

		foreach ($product_list as &$product)
		{
			if ((int)$product['id_address_delivery'] == 0)
				$product['id_address_delivery'] = (int)$this->id_address_delivery;

			if (!isset($warehouse_count_by_address[$product['id_address_delivery']]))
				$warehouse_count_by_address[$product['id_address_delivery']] = array();

			$product['warehouse_list'] = array();

			if ($stock_management_active &&
				((int)$product['advanced_stock_management'] == 1 || Pack::usesAdvancedStockManagement((int)$product['id_product'])))
			{
				$warehouse_list = Warehouse::getProductWarehouseList($product['id_product'], $product['id_product_attribute'], $this->id_shop);
				if (count($warehouse_list) == 0)
					$warehouse_list = Warehouse::getProductWarehouseList($product['id_product'], $product['id_product_attribute']);
				// Does the product is in stock ?
				// If yes, get only warehouse where the product is in stock

				$warehouse_in_stock = array();
				$manager = StockManagerFactory::getManager();

				foreach ($warehouse_list as $key => $warehouse)
				{
					$product_real_quantities = $manager->getProductRealQuantities(
						$product['id_product'],
						$product['id_product_attribute'],
						array($warehouse['id_warehouse']),
						true
					);

					if ($product_real_quantities > 0 || Pack::isPack((int)$product['id_product']))
						$warehouse_in_stock[] = $warehouse;
				}

				if (!empty($warehouse_in_stock))
				{
					$warehouse_list = $warehouse_in_stock;
					$product['in_stock'] = true;
				}
				else
					$product['in_stock'] = false;
			}
			else
			{
				//simulate default warehouse
				$warehouse_list = array(0);
				$product['in_stock'] = StockAvailable::getQuantityAvailableByProduct($product['id_product'], $product['id_product_attribute']) > 0;
			}

			foreach ($warehouse_list as $warehouse)
			{
				if (!isset($warehouse_carrier_list[$warehouse['id_warehouse']]))
				{
					$warehouse_object = new Warehouse($warehouse['id_warehouse']);
					$warehouse_carrier_list[$warehouse['id_warehouse']] = $warehouse_object->getCarriers();
				}

				$product['warehouse_list'][] = $warehouse['id_warehouse'];
				if (!isset($warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']]))
					$warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']] = 0;

				$warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']]++;
			}
		}
		unset($product);

		arsort($warehouse_count_by_address);

		// Guardamos si hay transportistas gratuitos y de pago mezclados
		$free_carriers = array();
		$payment_carriers = array();

		// Step 2 : Group product by warehouse
		$grouped_by_warehouse = array();
		foreach ($product_list as &$product)
		{
			if (!isset($grouped_by_warehouse[$product['id_address_delivery']]))
				$grouped_by_warehouse[$product['id_address_delivery']] = array(
					'in_stock' => array(),
					'out_of_stock' => array(),
				);

			$product['carrier_list'] = array();
			$id_warehouse = 0;
			foreach ($warehouse_count_by_address[$product['id_address_delivery']] as $id_war => $val)
			{
				if (in_array((int)$id_war, $product['warehouse_list']))
				{
					$product['carrier_list'] = array_merge($product['carrier_list'], Carrier::getAvailableCarrierList(new Product($product['id_product']), $id_war, $product['id_address_delivery'], null, $this));
					if (!$id_warehouse)
						$id_warehouse = (int)$id_war;
				}
			}

			if (!isset($grouped_by_warehouse[$product['id_address_delivery']]['in_stock'][$id_warehouse]))
			{
				$grouped_by_warehouse[$product['id_address_delivery']]['in_stock'][$id_warehouse] = array();
				$grouped_by_warehouse[$product['id_address_delivery']]['out_of_stock'][$id_warehouse] = array();
			}

			if (!$this->allow_seperated_package)
				$key = 'in_stock';
			else
				$key = $product['in_stock'] ? 'in_stock' : 'out_of_stock';

			if (empty($product['carrier_list']))
				$product['carrier_list'] = array(0);

			foreach( $product['carrier_list'] as $id_carrier )
			{
				if ( !in_array($id_carrier, $free_carriers) && !in_array($id_carrier, $payment_carriers) )
				{
					// Si el carrier no esta clasificado lo añadimos.
					$oCarrier = new Carrier( (int)$id_carrier );
					if ( $oCarrier->is_free )
					{
						$free_carriers[] = (int)$id_carrier;
					} else {
						$payment_carriers[] = (int)$id_carrier;
					}
				}
			}

			$grouped_by_warehouse[$product['id_address_delivery']][$key][$id_warehouse][] = $product;
		}
		unset($product);

		$first_payment_carriers = 0;
		if ( count($free_carriers) && count($payment_carriers) )
		{
			$first_payment_carriers = $payment_carriers[0];
		}

		// Step 3 : grouped product from grouped_by_warehouse by available carriers
		$grouped_by_carriers = array();
		foreach ($grouped_by_warehouse as $id_address_delivery => $products_in_stock_list)
		{
			if (!isset($grouped_by_carriers[$id_address_delivery]))
				$grouped_by_carriers[$id_address_delivery] = array(
					'in_stock' => array(),
					'out_of_stock' => array(),
				);
			foreach ($products_in_stock_list as $key => $warehouse_list)
			{
				if (!isset($grouped_by_carriers[$id_address_delivery][$key]))
					$grouped_by_carriers[$id_address_delivery][$key] = array();
				foreach ($warehouse_list as $id_warehouse => $product_list)
				{
					if (!isset($grouped_by_carriers[$id_address_delivery][$key][$id_warehouse]))
						$grouped_by_carriers[$id_address_delivery][$key][$id_warehouse] = array();
					foreach ($product_list as $product)
					{
						// Si tenemos mix de transportistas de pago y demas, quitamos los gratuitos.
						if ( !empty($first_payment_carriers) )
						{
							$carrier_list = array();
							foreach( $product['carrier_list'] as $id_carrier )
							{
								if ( in_array($id_carrier,$payment_carriers) )
								{
									$carrier_list[] = (string)$id_carrier;
								}
							}
							// Si el producto solo tiene gratuitos, fijamos el primero que haya de pago (en teoria solo hay uno)
							if ( empty($carrier_list) )
							{
								$carrier_list[]=(string)$first_payment_carriers;
							}
							$product['carrier_list']=$carrier_list;
						}

						$package_carriers_key = implode(',', $product['carrier_list']);

						if (!isset($grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key]))
							$grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key] = array(
								'product_list' => array(),
								'carrier_list' => $product['carrier_list'],
								'warehouse_list' => $product['warehouse_list']
							);

						$grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key]['product_list'][] = $product;
					}
				}
			}
		}

		$package_list = array();
		// Step 4 : merge product from grouped_by_carriers into $package to minimize the number of package
		foreach ($grouped_by_carriers as $id_address_delivery => $products_in_stock_list)
		{
			if (!isset($package_list[$id_address_delivery]))
				$package_list[$id_address_delivery] = array(
					'in_stock' => array(),
					'out_of_stock' => array(),
				);

			foreach ($products_in_stock_list as $key => $warehouse_list)
			{
				if (!isset($package_list[$id_address_delivery][$key]))
					$package_list[$id_address_delivery][$key] = array();
				// Count occurance of each carriers to minimize the number of packages
				$carrier_count = array();
				foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers)
				{
					foreach ($products_grouped_by_carriers as $data)
					{
						foreach ($data['carrier_list'] as $id_carrier)
						{
							if (!isset($carrier_count[$id_carrier]))
								$carrier_count[$id_carrier] = 0;
							$carrier_count[$id_carrier]++;
						}
					}
				}
				arsort($carrier_count);
				foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers)
				{
					if (!isset($package_list[$id_address_delivery][$key][$id_warehouse]))
						$package_list[$id_address_delivery][$key][$id_warehouse] = array();
					foreach ($products_grouped_by_carriers as $data)
					{
						foreach ($carrier_count as $id_carrier => $rate)
						{
							if (in_array($id_carrier, $data['carrier_list']))
							{
								if (!isset($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]))
									$package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier] = array(
										'carrier_list' => $data['carrier_list'],
										'warehouse_list' => $data['warehouse_list'],
										'product_list' => array(),
									);
								$package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['carrier_list'] =
									array_intersect($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['carrier_list'], $data['carrier_list']);
								$package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['product_list'] =
									array_merge($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['product_list'], $data['product_list']);

								break;
							}
						}
					}
				}
			}
		}

		// Step 5 : Reduce depth of $package_list
		$final_package_list = array();
		foreach ($package_list as $id_address_delivery => $products_in_stock_list)
		{
			if (!isset($final_package_list[$id_address_delivery]))
				$final_package_list[$id_address_delivery] = array();

			foreach ($products_in_stock_list as $key => $warehouse_list)
				foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers)
					foreach ($products_grouped_by_carriers as $data)
					{
						$final_package_list[$id_address_delivery][] = array(
							'product_list' => $data['product_list'],
							'carrier_list' => $data['carrier_list'],
							'warehouse_list' => $data['warehouse_list'],
							'id_warehouse' => $id_warehouse,
						);
					}
		}

		$cache[(int)$this->id] = $final_package_list;
		return $final_package_list;
	}

Thus if there are more than one carrier, in our case it means that the first one is the free one and the other one will charge us with a fee. Then we assume that the final carrier applied will only be the non-free removing the free carrier. This way we will avoid splitted orders.

(Note: I do my best trying to write English versions of my articles. If you see any mistake and want to help me please write down a comment with the details)

7 comments

  1. Hi,
    I tired your code and my problem is that shipping not calculating with correct price.
    I added a product with shipping rate : free
    and second product with 8€ shipping rate
    but after use your code, i checked my total and the total shipping rate show 20€ WHY?

    Thanks

  2. Hi, it’s very strange. The code here only assures that the products only get assigned payment carriers.

    Maybe do you have some cart rule that may be interfering?

    Do you have any other carriers configured?

  3. Hi David,
    I have 4 carriers and they are: Free, 24hr,72hr and from shop. Can you help me for solve this problem.
    Thanks

  4. Does any combination of these four carriers sum the amount of 20€?

    If you finish the checkout process does the system build only one order or is still creating several orders?

  5. Another option is to check the Shipping locations and costs tab on the carrier configuration section. Maybe you have some extra costs or ranges configured inside it.

  6. Hola! Tengo un problema parecido. Se trata de un Prestashop Dropshipping y cuando un cliente pide productos de diferentes proveedores, el pedido se divide según los artículos de cada proveedor, ya que necesita tener un envío para cada proveedor… no se si me entiendes. La cuestión es, se puede aplicar este código a mi caso?

    un saludo!

Leave a Reply

Your email address will not be published. Required fields are marked *