diff --git a/backend/controllers/ParserController.php b/backend/controllers/ParserController.php index bf306f7..09ef226 100644 --- a/backend/controllers/ParserController.php +++ b/backend/controllers/ParserController.php @@ -101,7 +101,6 @@ class ParserController extends BaseController } $model->file->saveAs($model->file_path); - // для авто загрузки, обработка завершена if ($model->mode) { $model->success = true; @@ -112,7 +111,10 @@ class ParserController extends BaseController // === ручная загрузка =========== //запускаем парсинг // доп. опции для парсера - $options = []; + $options = ['converter_conf' => + ['importer_id' => $files_model->importer_id] + ]; + if( ! $model->action ) // обработка с кастомным разделителем $options['$delimiter'] = $model->delimiter; @@ -124,12 +126,11 @@ class ParserController extends BaseController } else { // не прошла валидация форма загрузки файлов - //@todo - отправка на страницу ошибок - $errors_arr = $model->getErrors(); - foreach ($errors_arr as $error) { - CustomVarDamp::dump(array_values($error)); + $errors_str = ''; + foreach ($model->getErrors() as $error) { + $errors_str .= implode( array_values($error) ); } - die; + throw new ErrorException( $errors_str ); } // листаем пагинатором, или повторно вызываем - считываем из кеша отпрасенные данные } else if (Yii::$app->getCache()->get('parser_data')) { @@ -145,8 +146,11 @@ class ParserController extends BaseController ], ]); + + $last_index = end( array_flip( $data[0] ) ); + $header_counts = $last_index + 1; //формируем заголовок для пользователя, где он сможет выбрать соответсвие полей (выпадающий список) - $header_model = DynamicFormHelper::CreateDynamicModel(count($data[0])); + $header_model = DynamicFormHelper::CreateDynamicModel( $header_counts ); return $this->render('results', ['model' => $data, diff --git a/backend/models/Details.php b/backend/models/Details.php index ff6e09e..4205cb9 100644 --- a/backend/models/Details.php +++ b/backend/models/Details.php @@ -2,6 +2,7 @@ namespace backend\models; +use common\components\CustomVarDamp; use Yii; use backend\components\base\BaseActiveRecord; @@ -22,8 +23,22 @@ use backend\components\base\BaseActiveRecord; */ class Details extends BaseActiveRecord { - const KEY_COLUMN = ['IMPORT_ID','BRAND','ARTICLE']; + /** + *обязательные колонки + */ + const KEY_COLUMN = ['IMPORT_ID', 'BRAND', 'ARTICLE']; + + /** + * int - размер пакета запроса + */ const BATCH = 500; + + /** + * @var bool - признак необходимости удалить префикс Артикула перед вставкой + */ + public $delete_prefix = false; + public $delete_price = false; + /** * @inheritdoc */ @@ -38,8 +53,8 @@ class Details extends BaseActiveRecord public function rules() { return [ - [[ 'BRAND', 'ARTICLE', 'PRICE', 'DESCR', 'BOX'], 'required'], - // [['IMPORT_ID', 'BOX', 'ADD_BOX'], 'integer'], + [['BRAND', 'ARTICLE', 'PRICE', 'DESCR', 'BOX'], 'required'], + // [['IMPORT_ID', 'BOX', 'ADD_BOX'], 'integer'], [['PRICE'], 'number'], [['BOX'], 'integer'], [['timestamp'], 'safe'], @@ -69,13 +84,38 @@ class Details extends BaseActiveRecord ]; } - public function ManualInsert ($data) + /** + *удаление (если $delete_price установлен)б а затем вставка данных с апдейтом прямымыми запросоми SQL + * @param $data - массив вставляемых данных, вставка будет прозводится пакетами размером указанным в константе BATCH + * @param $importer_id - (int) - идентификатор поставщика у которого будет сперва удалены прайсы а потом вставлены из массива $data + * @throws \yii\db\Exception + */ + public function ManualInsert($data, $importer_id) + { + if ($this->delete_price) { + // запустим пакетное удаление всех прайсов поставщика + do { + $query = Yii::$app->db->createCommand()->delete(self::tableName(), "IMPORT_ID = {$importer_id}")->sql . ' Limit ' . $this::BATCH; + $res = Yii::$app->db->createCommand($query)->execute(); + } while ($res); + + } + + $this->ManualInsertWithUpdate($data); + } + + /** + * вставка данных с апдейтом прямым запросом SQL + * @param $data - массив вставляемых данный, вставка будет прозводится пакетами размером указанным в константе BATCH + * @throws \yii\db\Exception + */ + private function ManualInsertWithUpdate($data) { // \common\components\CustomVarDamp::dumpAndDie($data); $table_name = self::tableName(); - $keys_arr = array_keys( $data[0] ); + $keys_arr = array_keys($data[0]); // найдем те поля которые не являются ключами. Их нужно будет при дубляже апдейтить - $fields_arr_to_update = array_diff( $keys_arr, $this::KEY_COLUMN ); + $fields_arr_to_update = array_diff($keys_arr, $this::KEY_COLUMN); $query_update = ' on duplicate key update '; foreach ($fields_arr_to_update as $field) { @@ -87,18 +127,24 @@ class Details extends BaseActiveRecord // запросы будем выполнять пакетами // размер пакета установлен в константе // разобъем массив на пакеты и будем их проходить - $data = array_chunk($data, $this::BATCH ); - foreach( $data as $current_batch_array ){ + $data = array_chunk($data, $this::BATCH); + foreach ($data as $current_batch_array) { - //воспользуемся пакетной вставкой от фреймворка, плюс сразу с экранированием и защитой от инъекций + //воспользуемся пакетной вставкой от фреймворка $query_insert = Yii::$app->db->createCommand()->batchInsert($table_name, $keys_arr, $current_batch_array)->sql; + if ($this->delete_prefix) { + $query_insert = $this->prepareArticul( $query_insert ); + } // добавим фрагмент с апдейтом при дубляже $query = "{$query_insert} {$query_update}"; // \common\components\CustomVarDamp::dumpAndDie($query); - $res = Yii::$app->db->createCommand($query)->execute(); + Yii::$app->db->createCommand($query)->execute(); } } - + private function prepareArticul( $query_insert ){ + //CustomVarDamp::dumpAndDie($query_insert); + return $query_insert; + } } diff --git a/backend/models/UploadFileParsingForm.php b/backend/models/UploadFileParsingForm.php index 19d32c2..3dd3d62 100644 --- a/backend/models/UploadFileParsingForm.php +++ b/backend/models/UploadFileParsingForm.php @@ -1,6 +1,7 @@ multiparser->parse( $this->file_path, $options ); if( !is_array( $data ) ){ - $data = ['No results']; + throw new ErrorException("Ошибка чтения из файла прайса {$this->file_path}"); } + // файл больше не нужен - данные прочитаны и сохранены в кеш + if( file_exists($this->file_path) ) + unlink($this->file_path); return $data; } diff --git a/common/components/PriceWriter.php b/common/components/PriceWriter.php index c061ae9..a49ef0d 100644 --- a/common/components/PriceWriter.php +++ b/common/components/PriceWriter.php @@ -14,22 +14,45 @@ use backend\models\ImportersFiles; use backend\models\Importers; use backend\models\Details; -class PriceWriter { +/** + * Class PriceWriter + * @package common\components + * записывает в БД отпарсенные данные + * запись происходит в несколько таблиц + */ +class PriceWriter +{ + /** + * @var - int - 0 - интерактивный режим, 1 - консольный + */ public $mode; + + /** + * @var - массив с настройками записи + */ public $configuration; + + /** + * @var - массив с данными которые нужно записать + */ public $data; - public function writeDataToDB () + function __construct() + { + set_time_limit(300); + } + + public function writeDataToDB() { // 1. запишем дату старта в таблицу файлов поставщика (ImportersFiles) // id загруженного файла получим из конфигурации - $files_model = ImportersFiles::findOne( $this->configuration['record_id'] ); + $files_model = ImportersFiles::findOne($this->configuration['record_id']); $update_date = date('Y-m-d H:i:s'); $files_model->time_start = $update_date; // запишем дату начала загрузки if (!$files_model->save()) { - throw new \ErrorException(implode( ', ', $files_model->getErrors())); + throw new \ErrorException(implode(', ', $files_model->getErrors())); } // 2. запишем полученные данные в таблицу товаров (Details) @@ -43,7 +66,7 @@ class PriceWriter { $row['BOX'] = \Yii::$app->multiparser->convertToInteger($row['BOX']); // присвоим полный артикул $row['FULL_ARTICLE'] = $row['ARTICLE']; - if(isset($row['ADD_BOX'])) + if (isset($row['ADD_BOX'])) $row['ADD_BOX'] = \Yii::$app->multiparser->convertToInteger($row['ADD_BOX']); // проверим все ли обязательные колонки были указаны пользователем @@ -55,31 +78,35 @@ class PriceWriter { } } - // дополним данные значением импортера и даты обновления цены - $this->data = \Yii::$app->multiparser->addColumns($this->data, ['IMPORT_ID' => $this->configuration['importer_id'], 'timestamp' => $update_date]); + // дополним данные значением импортера и даты обновления цены + $this->data = \Yii::$app->multiparser->addColumns($this->data, ['IMPORT_ID' => $this->configuration['importer_id'], 'timestamp' => $update_date]); + try { + //@todo add transaction - try { - //@todo add transaction - // попытаемся вставить данные в БД с апдейтом по ключам - $details_model->ManualInsert($this->data); - - // 3. зафиксируем дату конца загрузки в файлах поставщика + if ((int)$this->configuration['delete_prefix']) { + $details_model->delete_prefix = true; + } + if ((int)$this->configuration['delete_price']) { + $details_model->delete_price = true; + } + //2. попытаемся вставить данные в БД с апдейтом по ключам + $details_model->ManualInsert($this->data, $this->configuration['importer_id']); - if (!$files_model->save()) { - throw new \ErrorException(implode( ', ', $files_model->getErrors())); - } + // 3. зафиксируем дату конца загрузки в файлах поставщика + if (!$files_model->save()) { + throw new \ErrorException(implode(', ', $files_model->getErrors())); + } - // 4. зафиксируем дату загрузки в таблице поставщиков - $imp_model = Importers::findOne($this->configuration['importer_id']); - $imp_model->price_date_update = $update_date; + // 4. зафиксируем дату загрузки в таблице поставщиков + $imp_model = Importers::findOne($this->configuration['importer_id']); + $imp_model->price_date_update = $update_date; - if (!$imp_model->save()) { - throw new \ErrorException(implode( ', ', $imp_model->getErrors())); - } - } catch (ErrorException $e) { - throw new \ErrorException( $e->getMessage() ); + if (!$imp_model->save()) { + throw new \ErrorException(implode(', ', $imp_model->getErrors())); } - + } catch (ErrorException $e) { + throw new \ErrorException($e->getMessage()); + } return true; diff --git a/common/components/parsers/CustomConverter.php b/common/components/parsers/CustomConverter.php index 07f8718..8bb595d 100644 --- a/common/components/parsers/CustomConverter.php +++ b/common/components/parsers/CustomConverter.php @@ -1,10 +1,13 @@ $sub_value) { if (isset($key_array[$sub_key])) { // если такой ключ в базовом массиве (массиве ключей) есть, то заменим новым, иначе просто удалим $new_key = $key_array[$sub_key]; - if( !array_key_exists( $new_key , $res ) ){ - $res[ $new_key ] = $value[$sub_key]; + if (!array_key_exists($new_key, $res)) { + $res[$new_key] = $value[$sub_key]; } } - unset( $res[$sub_key] ); + unset($res[$sub_key]); $value = $res; } }, - $key_array); + $key_array); return $value_arr; } @@ -54,7 +62,7 @@ class CustomConverter extends Converter { * @param $add_array - массив с колонками (ключи) и значениями колонок * @return mixed */ - public function addColumns ( array $value_arr , array $add_array ) + public function addColumns(array $value_arr, array $add_array) { $i = 0; while ($i < count($value_arr)) { @@ -66,7 +74,7 @@ class CustomConverter extends Converter { return $value_arr; } - public static function convertToDetails ( array $row ) + public static function convertToDetails(array $row) { // присвоим полный артикул $row['FULL_ARTICLE'] = $row['ARTICLE']; @@ -75,19 +83,19 @@ class CustomConverter extends Converter { // проверим все ли обязательные колонки были указаны пользователем $details_model->load(['Details' => $row]); - if (!$details_model->validate()){ + if (!$details_model->validate()) { $errors = ''; - foreach ( $details_model->errors as $key => $arr_errors ) { - $errors .= "Аттрибут $key - " . implode( ' , ', $arr_errors ); + foreach ($details_model->errors as $key => $arr_errors) { + $errors .= "Аттрибут $key - " . implode(' , ', $arr_errors); } - throw new \ErrorException( $errors ); + throw new \ErrorException($errors); } return $row; } - public function ConvertToMultiply ( array $row ) + public function ConvertToMultiply(array $row) { - $PRICE = $row[ 'PRICE' ]; + $PRICE = $row['PRICE']; $sign = self::$sign; $multiplier = self::$multiplier; //CustomVarDamp::dumpAndDie(self); @@ -96,28 +104,68 @@ class CustomConverter extends Converter { if ($multiplier > 0) { $PRICE += $multiplier; } - } - else if ($sign == '-') { + } else if ($sign == '-') { if ($multiplier > 0) { $PRICE -= $multiplier; } - } - else if ($sign == '*') { + } else if ($sign == '*') { if ($multiplier > 0) { $PRICE *= $multiplier; } - } - else if ($sign == '/') { + } else if ($sign == '/') { if ($multiplier > 0) { $PRICE /= $multiplier; } } } - $row[ 'PRICE' ] = $PRICE; + $row['PRICE'] = $PRICE; + + return $row; + + } + + public static function convertToArticul(array $row) + { + if (isset($row['ARTICLE']) && isset($row['BRAND']) && isset(self::$importer_id)) { + + // 1. Уберем префикс который разделен пробелом (если он есть) + $words = explode(" ", $row['ARTICLE']); + if (count($words) > 1) { + array_shift($words); + $row['ARTICLE'] = implode(" ", $words); + } + + // 2. Уберем брендовый префикс (если он есть) + $prefix = ''; + // запрос закешируем + $prefix = ImportersPrefix::getDb()->cache( function ($db, $configuration, $row ) { + return ImportersPrefix::find()->where([ 'importer_id' => self::$importer_id, + 'brand' => $row['BRAND'] ])->one(); + }); + if ($prefix) { + $row['BRAND'] = str_replace($prefix, "", $row['BRAND']); + } + } return $row; + } + + public static function convertToBrand($value) + { + $res = $value; + $res = trim(strtoupper($res)); + $res = str_replace("Ä", "A", str_replace("Ö", "O", str_replace("Ü", "U", str_replace("Ë", "E", str_replace("Ò", "O", $res))))); + $res = str_replace(array('@', '#', '~', '"', "'", "?", "!"), '', $res); + + return $res; + } + + public static function convertToString($value) + { + $value = parent::convertToString($value); + return str_replace(array('!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '~', '`', '"', "'", ' ', '№', '%', ';', ':', '[', ']', '{', '}', '*', '?', '/', '\'', '|', '.', ',', '<', '>', '\\'), '', $value); } diff --git a/common/components/parsers/CustomCsvParser.php b/common/components/parsers/CustomCsvParser.php index 6a7caf6..ed92588 100644 --- a/common/components/parsers/CustomCsvParser.php +++ b/common/components/parsers/CustomCsvParser.php @@ -16,7 +16,7 @@ use yii\base\ErrorException; class CustomCsvParser extends \yii\multiparser\CsvParser { - public $last_line = 10; + public $last_line = 100; //public $hasHeaderRow = true; // public $keys = ['first','second', 'third', 'forth', 'fifth']; public function setupConverter() diff --git a/common/components/parsers/config.php b/common/components/parsers/config.php index 4a423cb..98e7389 100644 --- a/common/components/parsers/config.php +++ b/common/components/parsers/config.php @@ -17,9 +17,11 @@ 'hasKey' => 1, 'configuration' => ["string" => 'DESCR', "float" => 'PRICE', + "brand" => 'BRAND', "integer" => ['BOX','ADD_BOX'], "multiply" => [], - "details" => [] // @todo сделать отдельно конфигурирование валидации + "details" => [], + "articul" => [] ] ],], diff --git a/console/controllers/ParserController.php b/console/controllers/ParserController.php index aa725c8..cd04973 100644 --- a/console/controllers/ParserController.php +++ b/console/controllers/ParserController.php @@ -32,8 +32,7 @@ class ParserController extends Controller 'importer_id' => $importer_id, 'parser_config' => ['keys' => $keys, 'converter_conf' => - ['sign' => $sign, - 'multiplier' => $multiplier], + [ 'sign' => $sign, 'multiplier' => $multiplier, 'importer_id' => $importer_id ], 'mode' => 'console'] ]; if ($this->parseFileConsole($file_path, $config)) { diff --git a/console/migrations/m151013_062829_deletePrefixFunction.php b/console/migrations/m151013_062829_deletePrefixFunction.php new file mode 100644 index 0000000..fb7cc1d --- /dev/null +++ b/console/migrations/m151013_062829_deletePrefixFunction.php @@ -0,0 +1,59 @@ +execute($find_prefix); + $this->execute($delete_prefix); + + } + + public function safedown() + { + + $find_prefix = <<< SQL + drop FUNCTION FindPrefix; +SQL; + + $delete_prefix = <<< SQL + drop FUNCTION DeletePrefix; +SQL; + + $this->execute($find_prefix); + $this->execute($delete_prefix); + + + } + + +} -- libgit2 0.21.4