Prestashop tiene un importador de productos a partir de un CSV que funciona genial. El problema es que ese importador siempre sobreescribe los nombres de los productos. Eso implica que si importas los productos y luego los personalizas esos cambios se perderán (por ejemplo si queremos actualizar sólo precios). 

New parameter added

Lo que queríamos conseguir:

  • Importar inicialmente todos los productos de la tienda desde un archivo CSV de tarifas de un proveedor.
  • Actualizar cada poco tiempo los precios de los productos con ese mismo CSV actualizado sin perder la información modificada.
  • Detectar nuevos productos en el CSV y agregarlos a la base de datos.
  • Opcionalmente permitir al usuario que regenere los nombres de productos si lo desea.

1Cambios necesarios

Required changes summary

  • Vamos a agregar al importador un nuevo parámetro llamado "keep_names". Eso permitirá al administrador elegir si quiere o no sobreescribir los nombres de sus productos. El comportamiento por defecto será el propio de Prestashop (sobreescribir los nombres).
  • Modificar el proceso de importado para que los productos hereden el nombre en caso de que la opción "keep_names" esté activada o en caso de que el nombre esté vacío.

El resto de este artículo explica los cambios necesarios para conseguir lo arriba expuesto. Si no quieres seguir leyendo puedes directamente::

descargar el código

 

2Agregar un parámetro al importador de Prestashop

Básicamente necesitaremos hacer un override ciertos archivos del template de la administración. Estos son:

admin/themes/default/template/controllers/import/helpers/form/form.tpl
admin/themes/default/template/controllers/import/helpers/view/view.tpl

Reemplazar "admin" con el nombre de tu directorio admin.

Para modificarlos y hacer que se carguen en lugar de los que vienen por defecto los tenemos que copiar en:

override/controllers/admin/templates/import/helpers/form/form.tpl
override/controllers/admin/templates/import/helpers/view/view.tpl

Con esto conseguiremos que al actualizar Prestashop no perdamos los cambios que vamos a efectuar.

Para no recargar este artículo con código hemos subido el código completo a un gist. Así puedes ir mirando el código fuente sin necesidad de descargarlo.

En el artchivo form.tpl hemos agregado el nuevo parámetro con:


También hemos agregado la lógica javascript para esconder el campo cuando no se están importando productos:

		if ($("#entity > option:selected").val() == 1)
		{
			$("label[for=match_ref],#match_ref").show();
			$("label[for=keep_names],#keep_names").show();
		}
		else
		{
			$("label[for=match_ref],#match_ref").hide();
			$("label[for=keep_names],#keep_names").hide();
		}

En el archivo view.tpl hemos agregado el código para recibir y propagar el campo keep_names como campo oculto:

		{if $fields_value.keep_names}
			
		{/if}

3Personalizar el controlador importador

Este es el archivo donde se realmente se importan los datos. Lo que vamos a hacer es copiar los métodos que queremos personalizar del controlador principal en:

controllers/admin/AdminImportController.php

a la clase personalizada en:

override/controllers/admin/AdminImportController.php

Sólo necesitamos personalizar dos métodos del controlador original: renderView() y productImport(). Siempre es recomendable personalizar sólo los métodos que realmente lo requieran. De esta forma el resto de métodos siguen cargándose desde el núcleo de prestashop que es actualizado en caso de ser necesario.

Puedes ver el archivo AdminImportController.php final en el gist.

En el método renderView() recibimos el valor de keep_names receive para propagarlo a la vista:

'keep_names' => Tools::getValue('keep_names'),

También hemos tenido que modificar la llamada al método renderView() padre porque al haber extendido la clase padre ahora llamaremos al "Abuelo" (AdminController) en su lugar:

return AdminController::renderView();

En el método productImport() reemplazaremos este código:

			$field_error = $product->validateFields(UNFRIENDLY_ERROR, true);
			$lang_field_error = $product->validateFieldsLang(UNFRIENDLY_ERROR, true);
			if ($field_error === true && $lang_field_error === true)
			{
				// check quantity
				if ($product->quantity == null)
					$product->quantity = 0;
				// If match ref is specified && ref product && ref product already in base, trying to update
				if (Tools::getValue('match_ref') == 1 && $product->reference && $product->existsRefInDatabase($product->reference))
				{
					$datas = Db::getInstance()->getRow('
						SELECT product_shop.`date_add`, p.`id_product`
						FROM `'._DB_PREFIX_.'product` p
						'.Shop::addSqlAssociation('product', 'p').'
						WHERE p.`reference` = "'.$product->reference.'"
					');
					$product->id = (int)$datas['id_product'];
					$product->date_add = pSQL($datas['date_add']);
					$res = $product->update();
				} // Else If id product && id product already in base, trying to update
				else if ($product->id && Product::existsInDatabase((int)$product->id, 'product'))
				{
					$datas = Db::getInstance()->getRow('
						SELECT product_shop.`date_add`
						FROM `'._DB_PREFIX_.'product` p
						'.Shop::addSqlAssociation('product', 'p').'
						WHERE p.`id_product` = '.(int)$product->id);
					$product->date_add = pSQL($datas['date_add']);
					$res = $product->update();
				}
				// If no id_product or update failed
				if (!$res)
				{
					if (isset($product->date_add) && $product->date_add != '')
						$res = $product->add(false);
					else
						$res = $product->add();
				}
			}

con:

			$preloadProduct     = false;
			$preloadById        = ($product->id && Product::existsInDatabase((int)$product->id, 'product'));
			$preloadByreference = (Tools::getValue('match_ref') == 1 && $product->reference && $product->existsRefInDatabase($product->reference));
			if ($preloadByreference || $preloadById)
			{
				$preloadProduct = true;
				$sql = 'SELECT
							product_shop.`date_add`, p.`id_product`, pl.`name`
						FROM
							`'._DB_PREFIX_.'product` p
						LEFT JOIN
							'._DB_PREFIX_.'product_lang pl ON (pl.id_product = p.id_product AND pl.`id_lang` = ' . (int) $default_language_id . ')
						' . Shop::addSqlAssociation('product', 'p');
				if ($preloadByreference)
				{
					$sql .= 'WHERE p.`reference` = "' . $product->reference . '" ';
				}
				if ($preloadById)
				{
					$sql .= 'WHERE p.`id_product` = ' . (int) $product->id;
				}
				$datas = Db::getInstance()->getRow($sql);
				$product->id = (int)$datas['id_product'];
				$product->date_add = pSQL($datas['date_add']);
				// Check quantity
				if ($product->quantity == null)
				{
					$product->quantity = 0;
				}
				// Inherit product name if required or not set
				if (Tools::getValue('keep_names') || !isset($product->name) || empty($product->name))
				{
					$product->name = pSQL($datas['name']);
				}
			}
			$field_error = $product->validateFields(UNFRIENDLY_ERROR, true);
			$lang_field_error = $product->validateFieldsLang(UNFRIENDLY_ERROR, true);
			if ($field_error === true && $lang_field_error === true)
			{
				if ($preloadProduct)
				{
					$res = $product->update();
				}
				// If no id_product or update failed
				if (!$res)
				{
					if (isset($product->date_add) && $product->date_add != '')
						$res = $product->add(false);
					else
						$res = $product->add();
				}
			}

El código hace la precarga de los productos cuando es necesario. Esto nos permite heredar los nombres cuando así lo ha solicitado el usuario.

				// Inherit product name if required or not set
				if (Tools::getValue('keep_names') || !isset($product->name) || empty($product->name))
				{
					$product->name = pSQL($datas['name']);
				}