Solucionado! Evitar la creación múltiple de pedidos en Prestashop

En Prestashop ocasionalmente nos encontramos con un molesto problema. Los pedidos de los clientes se dividen en mas de uno, conservando el mismo código de referencia.

PedidosDuplicados

Tras realizar una exhaustiva investigación nos dimos cuenta de que el problema tenia un nombre propio: transportistas. Concretamente el tipo de transportista asociado a los productos que componen el carrito.

ConfiguracionTransportistaPrestashop por defecto separa los items del carrito en diferentes pedidos agrupándolos por su transportista asociado. En teoria es un buen sistema para ayudar en la gestión interna del eCommerce a la hora de enviar los productos.

El problema viene si como en nuestro caso solamente utilizamos un transportista pero generamos otro gratuito para los artículos que queramos vender sin el importe de transportista, como en el caso de los obsequios.

Transportistas

Este es uno de los numerosos casos en los que Prestashop decide un comportamiento sin dar la opción a desactivarlo o ajustarlo.

La única manera para conseguirlo pasa por modificar via los overrides la clase que gestiona esta funcionalidad. Específicamente la clase Cart.php y su función getPackageList(). Las líneas destacadas son las que se han añadido a la programación original.

	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;
	}

De esta manera en caso de existir más de un transportista, en nuestro caso significa que uno es el gratuito y otro de pago, con lo que asumimos que el transportista sera solo el de pago así no nos encontraremos con mas duplicidad de pedidos.

7 comentarios

  1. ¿Y si hay 2 transportistas de pago?

    1- Para Peninsula
    2- Para las Islas

    Tengo algunos productos que no quiero enviar a las islas y por ello les asigno el transportista 1 (peninsula).

    Me hacen un pedido mezclando productos de 1 transportista y del otro, el destino es Mallorca. Prestashop lo que realiza es un pedido duplicado solo cobrando los gastos de los productos que si se pueden enviar a Mallorca. Los otros los envia gratis.

    ¿Como puedo evitar esto? Que no me deje realizar el pedidos. Lo que si puedo hacer es permitir al Transportista 1 que envie a Islas y así por lo menos no palmo pasta. Pero preferiria que no me dejara terminar el pedidos.

  2. tengo un problema similar.

    Resulta que en una tienda online un cliente hizo un pedido de 3 productos (y su respectivo pago) pero al administrador de la tienda (y a mi tambien) me llegó el correo del pedido de tan solo 2 productos pero el email de Payu notificó el pago de los 3 productos, cosa que despertó la alerta a del administrador de la tienda del porqué había pagado de más si solo habían 2 productos en el email del pedido.

    Pues bien, me metí en la sección de Pedidos de Chocolate y resulta que el pedido con numero #339 tiene 2 productos pero en la parte inferior hay una sección llamada PEDIDOS RELACIONADOS, donde está el pedido con numero #340 y en ese pedido está el otro producto. La suma de los 2 pedidos efectivamente da lo que el cliente pagó, pero no sabemos el porqué Prestashop partió de esa manera los pedidos y porqué solo notificó en el que hay 2 productos y el otro no.

    la tienda solo tiene habilitado un transporte sin embargo hay creados 2, de los cuales el que está des-habilitado de envío gratis.

    no es un caso que pase a cada rato por eso se nos hace muy extraño.

    muchas gracias por su ayuda.

  3. Hola, existe algo similar para que no duplique los pedidos cuando se trata de articulos de varios almacenes distintos?
    He probado esta opción pero al modificar el cart.php me permite solo pedir artículos del almacen principal

  4. Hola David, tengo una web con ese problema y me gustaria solucionarlo, ¿me puedes ayudar?, ¿ donde y como me pongo encontacto contigo para darte los datos de acceso y modifiques lo que se necesite?, ya me enviaras el presupuesto. Muchas gracias.

  5. Hola muchachos tengo un problema explico:

    Tengo una tienda con 2 tipos de transportistas, los cuales estan asignados a prouctos en especifico.

    Al realizar una orden con 1 solo tipo de transportista todo se procesa de manera correcta.

    Al generar una orden que tenga 2 transportistas es donde se genera el problema con el monto TOTAL, sabemos que el monto total que es la suma de (Total products + Total shipping + Total tax ), en el resumen del pedido me lo da bien. Pero al darle al boton de CONFIRMAR MI ORDEN y me manda a la pantalla de Confirmación del pedido, el monto total no es correcto ( Me muestra el monto solo de los productos que estan asignado a 1 transportita en especifico, los otros productos asigandos al otro transportista y el costo de ese otro transportista no lo toma en cuenta)

    Me puedes ayudar por favor

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *