Commit 66adbd632678bf78088510b48e73b30ddad816b9

Authored by Mihail
1 parent d4d85fc1

sinhronize with server

backend/controllers/ParserController.php
... ... @@ -101,7 +101,6 @@ class ParserController extends BaseController
101 101 }
102 102  
103 103 $model->file->saveAs($model->file_path);
104   -
105 104 // для авто загрузки, обработка завершена
106 105 if ($model->mode) {
107 106 $model->success = true;
... ... @@ -112,7 +111,10 @@ class ParserController extends BaseController
112 111 // === ручная загрузка ===========
113 112 //запускаем парсинг
114 113 // доп. опции для парсера
115   - $options = [];
  114 + $options = ['converter_conf' =>
  115 + ['importer_id' => $files_model->importer_id]
  116 + ];
  117 +
116 118 if( ! $model->action ) // обработка с кастомным разделителем
117 119 $options['$delimiter'] = $model->delimiter;
118 120  
... ... @@ -124,12 +126,11 @@ class ParserController extends BaseController
124 126  
125 127 } else {
126 128 // не прошла валидация форма загрузки файлов
127   - //@todo - отправка на страницу ошибок
128   - $errors_arr = $model->getErrors();
129   - foreach ($errors_arr as $error) {
130   - CustomVarDamp::dump(array_values($error));
  129 + $errors_str = '';
  130 + foreach ($model->getErrors() as $error) {
  131 + $errors_str .= implode( array_values($error) );
131 132 }
132   - die;
  133 + throw new ErrorException( $errors_str );
133 134 }
134 135 // листаем пагинатором, или повторно вызываем - считываем из кеша отпрасенные данные
135 136 } else if (Yii::$app->getCache()->get('parser_data')) {
... ... @@ -145,8 +146,11 @@ class ParserController extends BaseController
145 146 ],
146 147 ]);
147 148  
  149 +
  150 + $last_index = end( array_flip( $data[0] ) );
  151 + $header_counts = $last_index + 1;
148 152 //формируем заголовок для пользователя, где он сможет выбрать соответсвие полей (выпадающий список)
149   - $header_model = DynamicFormHelper::CreateDynamicModel(count($data[0]));
  153 + $header_model = DynamicFormHelper::CreateDynamicModel( $header_counts );
150 154  
151 155 return $this->render('results',
152 156 ['model' => $data,
... ...
backend/models/Details.php
... ... @@ -2,6 +2,7 @@
2 2  
3 3 namespace backend\models;
4 4  
  5 +use common\components\CustomVarDamp;
5 6 use Yii;
6 7 use backend\components\base\BaseActiveRecord;
7 8  
... ... @@ -22,8 +23,22 @@ use backend\components\base\BaseActiveRecord;
22 23 */
23 24 class Details extends BaseActiveRecord
24 25 {
25   - const KEY_COLUMN = ['IMPORT_ID','BRAND','ARTICLE'];
  26 + /**
  27 + *обязательные колонки
  28 + */
  29 + const KEY_COLUMN = ['IMPORT_ID', 'BRAND', 'ARTICLE'];
  30 +
  31 + /**
  32 + * int - размер пакета запроса
  33 + */
26 34 const BATCH = 500;
  35 +
  36 + /**
  37 + * @var bool - признак необходимости удалить префикс Артикула перед вставкой
  38 + */
  39 + public $delete_prefix = false;
  40 + public $delete_price = false;
  41 +
27 42 /**
28 43 * @inheritdoc
29 44 */
... ... @@ -38,8 +53,8 @@ class Details extends BaseActiveRecord
38 53 public function rules()
39 54 {
40 55 return [
41   - [[ 'BRAND', 'ARTICLE', 'PRICE', 'DESCR', 'BOX'], 'required'],
42   - // [['IMPORT_ID', 'BOX', 'ADD_BOX'], 'integer'],
  56 + [['BRAND', 'ARTICLE', 'PRICE', 'DESCR', 'BOX'], 'required'],
  57 + // [['IMPORT_ID', 'BOX', 'ADD_BOX'], 'integer'],
43 58 [['PRICE'], 'number'],
44 59 [['BOX'], 'integer'],
45 60 [['timestamp'], 'safe'],
... ... @@ -69,13 +84,38 @@ class Details extends BaseActiveRecord
69 84 ];
70 85 }
71 86  
72   - public function ManualInsert ($data)
  87 + /**
  88 + *удаление (если $delete_price установлен)б а затем вставка данных с апдейтом прямымыми запросоми SQL
  89 + * @param $data - массив вставляемых данных, вставка будет прозводится пакетами размером указанным в константе BATCH
  90 + * @param $importer_id - (int) - идентификатор поставщика у которого будет сперва удалены прайсы а потом вставлены из массива $data
  91 + * @throws \yii\db\Exception
  92 + */
  93 + public function ManualInsert($data, $importer_id)
  94 + {
  95 + if ($this->delete_price) {
  96 + // запустим пакетное удаление всех прайсов поставщика
  97 + do {
  98 + $query = Yii::$app->db->createCommand()->delete(self::tableName(), "IMPORT_ID = {$importer_id}")->sql . ' Limit ' . $this::BATCH;
  99 + $res = Yii::$app->db->createCommand($query)->execute();
  100 + } while ($res);
  101 +
  102 + }
  103 +
  104 + $this->ManualInsertWithUpdate($data);
  105 + }
  106 +
  107 + /**
  108 + * вставка данных с апдейтом прямым запросом SQL
  109 + * @param $data - массив вставляемых данный, вставка будет прозводится пакетами размером указанным в константе BATCH
  110 + * @throws \yii\db\Exception
  111 + */
  112 + private function ManualInsertWithUpdate($data)
73 113 {
74 114 // \common\components\CustomVarDamp::dumpAndDie($data);
75 115 $table_name = self::tableName();
76   - $keys_arr = array_keys( $data[0] );
  116 + $keys_arr = array_keys($data[0]);
77 117 // найдем те поля которые не являются ключами. Их нужно будет при дубляже апдейтить
78   - $fields_arr_to_update = array_diff( $keys_arr, $this::KEY_COLUMN );
  118 + $fields_arr_to_update = array_diff($keys_arr, $this::KEY_COLUMN);
79 119  
80 120 $query_update = ' on duplicate key update ';
81 121 foreach ($fields_arr_to_update as $field) {
... ... @@ -87,18 +127,24 @@ class Details extends BaseActiveRecord
87 127 // запросы будем выполнять пакетами
88 128 // размер пакета установлен в константе
89 129 // разобъем массив на пакеты и будем их проходить
90   - $data = array_chunk($data, $this::BATCH );
91   - foreach( $data as $current_batch_array ){
  130 + $data = array_chunk($data, $this::BATCH);
  131 + foreach ($data as $current_batch_array) {
92 132  
93   - //воспользуемся пакетной вставкой от фреймворка, плюс сразу с экранированием и защитой от инъекций
  133 + //воспользуемся пакетной вставкой от фреймворка
94 134 $query_insert = Yii::$app->db->createCommand()->batchInsert($table_name, $keys_arr, $current_batch_array)->sql;
  135 + if ($this->delete_prefix) {
  136 + $query_insert = $this->prepareArticul( $query_insert );
  137 + }
95 138 // добавим фрагмент с апдейтом при дубляже
96 139 $query = "{$query_insert} {$query_update}";
97 140 // \common\components\CustomVarDamp::dumpAndDie($query);
98   - $res = Yii::$app->db->createCommand($query)->execute();
  141 + Yii::$app->db->createCommand($query)->execute();
99 142  
100 143 }
101 144 }
102 145  
103   -
  146 + private function prepareArticul( $query_insert ){
  147 + //CustomVarDamp::dumpAndDie($query_insert);
  148 + return $query_insert;
  149 + }
104 150 }
... ...
backend/models/UploadFileParsingForm.php
1 1 <?php
2 2 namespace backend\models;
3 3  
  4 +use yii\base\ErrorException;
4 5 use yii\base\Model;
5 6 use yii\web\UploadedFile;
6 7 use Yii;
... ... @@ -73,8 +74,11 @@ class UploadFileParsingForm extends Model
73 74  
74 75 $data = Yii::$app->multiparser->parse( $this->file_path, $options );
75 76 if( !is_array( $data ) ){
76   - $data = ['No results'];
  77 + throw new ErrorException("Ошибка чтения из файла прайса {$this->file_path}");
77 78 }
  79 + // файл больше не нужен - данные прочитаны и сохранены в кеш
  80 + if( file_exists($this->file_path) )
  81 + unlink($this->file_path);
78 82  
79 83 return $data;
80 84 }
... ...
common/components/PriceWriter.php
... ... @@ -14,22 +14,45 @@ use backend\models\ImportersFiles;
14 14 use backend\models\Importers;
15 15 use backend\models\Details;
16 16  
17   -class PriceWriter {
  17 +/**
  18 + * Class PriceWriter
  19 + * @package common\components
  20 + * записывает в БД отпарсенные данные
  21 + * запись происходит в несколько таблиц
  22 + */
  23 +class PriceWriter
  24 +{
  25 + /**
  26 + * @var - int - 0 - интерактивный режим, 1 - консольный
  27 + */
18 28 public $mode;
  29 +
  30 + /**
  31 + * @var - массив с настройками записи
  32 + */
19 33 public $configuration;
  34 +
  35 + /**
  36 + * @var - массив с данными которые нужно записать
  37 + */
20 38 public $data;
21 39  
22   - public function writeDataToDB ()
  40 + function __construct()
  41 + {
  42 + set_time_limit(300);
  43 + }
  44 +
  45 + public function writeDataToDB()
23 46 {
24 47 // 1. запишем дату старта в таблицу файлов поставщика (ImportersFiles)
25 48 // id загруженного файла получим из конфигурации
26   - $files_model = ImportersFiles::findOne( $this->configuration['record_id'] );
  49 + $files_model = ImportersFiles::findOne($this->configuration['record_id']);
27 50  
28 51 $update_date = date('Y-m-d H:i:s');
29 52 $files_model->time_start = $update_date;
30 53 // запишем дату начала загрузки
31 54 if (!$files_model->save()) {
32   - throw new \ErrorException(implode( ', ', $files_model->getErrors()));
  55 + throw new \ErrorException(implode(', ', $files_model->getErrors()));
33 56 }
34 57  
35 58 // 2. запишем полученные данные в таблицу товаров (Details)
... ... @@ -43,7 +66,7 @@ class PriceWriter {
43 66 $row['BOX'] = \Yii::$app->multiparser->convertToInteger($row['BOX']);
44 67 // присвоим полный артикул
45 68 $row['FULL_ARTICLE'] = $row['ARTICLE'];
46   - if(isset($row['ADD_BOX']))
  69 + if (isset($row['ADD_BOX']))
47 70 $row['ADD_BOX'] = \Yii::$app->multiparser->convertToInteger($row['ADD_BOX']);
48 71  
49 72 // проверим все ли обязательные колонки были указаны пользователем
... ... @@ -55,31 +78,35 @@ class PriceWriter {
55 78 }
56 79 }
57 80  
58   - // дополним данные значением импортера и даты обновления цены
59   - $this->data = \Yii::$app->multiparser->addColumns($this->data, ['IMPORT_ID' => $this->configuration['importer_id'], 'timestamp' => $update_date]);
  81 + // дополним данные значением импортера и даты обновления цены
  82 + $this->data = \Yii::$app->multiparser->addColumns($this->data, ['IMPORT_ID' => $this->configuration['importer_id'], 'timestamp' => $update_date]);
  83 + try {
  84 + //@todo add transaction
60 85  
61   - try {
62   - //@todo add transaction
63   - // попытаемся вставить данные в БД с апдейтом по ключам
64   - $details_model->ManualInsert($this->data);
65   -
66   - // 3. зафиксируем дату конца загрузки в файлах поставщика
  86 + if ((int)$this->configuration['delete_prefix']) {
  87 + $details_model->delete_prefix = true;
  88 + }
  89 + if ((int)$this->configuration['delete_price']) {
  90 + $details_model->delete_price = true;
  91 + }
  92 + //2. попытаемся вставить данные в БД с апдейтом по ключам
  93 + $details_model->ManualInsert($this->data, $this->configuration['importer_id']);
67 94  
68   - if (!$files_model->save()) {
69   - throw new \ErrorException(implode( ', ', $files_model->getErrors()));
70   - }
  95 + // 3. зафиксируем дату конца загрузки в файлах поставщика
  96 + if (!$files_model->save()) {
  97 + throw new \ErrorException(implode(', ', $files_model->getErrors()));
  98 + }
71 99  
72   - // 4. зафиксируем дату загрузки в таблице поставщиков
73   - $imp_model = Importers::findOne($this->configuration['importer_id']);
74   - $imp_model->price_date_update = $update_date;
  100 + // 4. зафиксируем дату загрузки в таблице поставщиков
  101 + $imp_model = Importers::findOne($this->configuration['importer_id']);
  102 + $imp_model->price_date_update = $update_date;
75 103  
76   - if (!$imp_model->save()) {
77   - throw new \ErrorException(implode( ', ', $imp_model->getErrors()));
78   - }
79   - } catch (ErrorException $e) {
80   - throw new \ErrorException( $e->getMessage() );
  104 + if (!$imp_model->save()) {
  105 + throw new \ErrorException(implode(', ', $imp_model->getErrors()));
81 106 }
82   -
  107 + } catch (ErrorException $e) {
  108 + throw new \ErrorException($e->getMessage());
  109 + }
83 110  
84 111  
85 112 return true;
... ...
common/components/parsers/CustomConverter.php
1 1 <?php
2 2 namespace common\components\parsers;
  3 +
3 4 use common\components\CustomVarDamp;
4 5 use yii\multiparser\Converter;
5 6 use backend\models\Details;
  7 +use backend\models\ImportersPrefix;
6 8  
7   -class CustomConverter extends Converter {
  9 +class CustomConverter extends Converter
  10 +{
8 11  
9 12 /**
10 13 * @param $value_arr - двумерный массив значений, которому нужно присвоить ключи
... ... @@ -13,38 +16,43 @@ class CustomConverter extends Converter {
13 16 */
14 17 public static $sign;
15 18 public static $multiplier;
  19 + public static $importer_id;
16 20  
17   - public static function convertToAssocArray ( array $value_arr, array $key_array, $key_prefix = '' )
  21 + public static function convertToAssocArray(array $value_arr, array $key_array, $key_prefix = '')
18 22 {
19 23 // очистка служебного префикса в массиве заголовков
20 24 if ($key_prefix) {
21 25 // @todo оптимизировать - два переворота массива - избыточно
22   - $key_array = array_flip( $key_array );
  26 + $key_array = array_flip($key_array);
23 27  
24   - array_walk( $key_array, function ( &$value, $key, $key_prefix ){ $value = str_replace( $key_prefix, '',$value ); }, $key_prefix );
  28 + array_walk($key_array, function (&$value, $key, $key_prefix) {
  29 + $value = str_replace($key_prefix, '', $value);
  30 + }, $key_prefix);
25 31  
26   - $key_array = array_flip( $key_array );
  32 + $key_array = array_flip($key_array);
27 33 //уберем пустые элементы
28   - $key_array = array_filter($key_array, function ($value){ return $value !==''; });
  34 + $key_array = array_filter($key_array, function ($value) {
  35 + return $value !== '';
  36 + });
29 37 }
30 38  
31   - array_walk( $value_arr,
32   - function ( &$value, $key, $key_array ) {
  39 + array_walk($value_arr,
  40 + function (&$value, $key, $key_array) {
33 41 $res = $value;
34 42 foreach ($res as $sub_key => $sub_value) {
35 43 if (isset($key_array[$sub_key])) {
36 44 // если такой ключ в базовом массиве (массиве ключей) есть, то заменим новым, иначе просто удалим
37 45 $new_key = $key_array[$sub_key];
38   - if( !array_key_exists( $new_key , $res ) ){
39   - $res[ $new_key ] = $value[$sub_key];
  46 + if (!array_key_exists($new_key, $res)) {
  47 + $res[$new_key] = $value[$sub_key];
40 48 }
41 49 }
42   - unset( $res[$sub_key] );
  50 + unset($res[$sub_key]);
43 51 $value = $res;
44 52 }
45 53  
46 54 },
47   - $key_array);
  55 + $key_array);
48 56  
49 57 return $value_arr;
50 58 }
... ... @@ -54,7 +62,7 @@ class CustomConverter extends Converter {
54 62 * @param $add_array - массив с колонками (ключи) и значениями колонок
55 63 * @return mixed
56 64 */
57   - public function addColumns ( array $value_arr , array $add_array )
  65 + public function addColumns(array $value_arr, array $add_array)
58 66 {
59 67 $i = 0;
60 68 while ($i < count($value_arr)) {
... ... @@ -66,7 +74,7 @@ class CustomConverter extends Converter {
66 74 return $value_arr;
67 75 }
68 76  
69   - public static function convertToDetails ( array $row )
  77 + public static function convertToDetails(array $row)
70 78 {
71 79 // присвоим полный артикул
72 80 $row['FULL_ARTICLE'] = $row['ARTICLE'];
... ... @@ -75,19 +83,19 @@ class CustomConverter extends Converter {
75 83 // проверим все ли обязательные колонки были указаны пользователем
76 84 $details_model->load(['Details' => $row]);
77 85  
78   - if (!$details_model->validate()){
  86 + if (!$details_model->validate()) {
79 87 $errors = '';
80   - foreach ( $details_model->errors as $key => $arr_errors ) {
81   - $errors .= "Аттрибут $key - " . implode( ' , ', $arr_errors );
  88 + foreach ($details_model->errors as $key => $arr_errors) {
  89 + $errors .= "Аттрибут $key - " . implode(' , ', $arr_errors);
82 90 }
83   - throw new \ErrorException( $errors );
  91 + throw new \ErrorException($errors);
84 92 }
85 93 return $row;
86 94 }
87 95  
88   - public function ConvertToMultiply ( array $row )
  96 + public function ConvertToMultiply(array $row)
89 97 {
90   - $PRICE = $row[ 'PRICE' ];
  98 + $PRICE = $row['PRICE'];
91 99 $sign = self::$sign;
92 100 $multiplier = self::$multiplier;
93 101 //CustomVarDamp::dumpAndDie(self);
... ... @@ -96,28 +104,68 @@ class CustomConverter extends Converter {
96 104 if ($multiplier > 0) {
97 105 $PRICE += $multiplier;
98 106 }
99   - }
100   - else if ($sign == '-') {
  107 + } else if ($sign == '-') {
101 108 if ($multiplier > 0) {
102 109 $PRICE -= $multiplier;
103 110 }
104   - }
105   - else if ($sign == '*') {
  111 + } else if ($sign == '*') {
106 112 if ($multiplier > 0) {
107 113 $PRICE *= $multiplier;
108 114 }
109   - }
110   - else if ($sign == '/') {
  115 + } else if ($sign == '/') {
111 116 if ($multiplier > 0) {
112 117 $PRICE /= $multiplier;
113 118 }
114 119 }
115 120 }
116 121  
117   - $row[ 'PRICE' ] = $PRICE;
  122 + $row['PRICE'] = $PRICE;
  123 +
  124 + return $row;
  125 +
  126 + }
  127 +
  128 + public static function convertToArticul(array $row)
  129 + {
  130 + if (isset($row['ARTICLE']) && isset($row['BRAND']) && isset(self::$importer_id)) {
  131 +
  132 + // 1. Уберем префикс который разделен пробелом (если он есть)
  133 + $words = explode(" ", $row['ARTICLE']);
  134 + if (count($words) > 1) {
  135 + array_shift($words);
  136 + $row['ARTICLE'] = implode(" ", $words);
  137 + }
  138 +
  139 + // 2. Уберем брендовый префикс (если он есть)
  140 + $prefix = '';
  141 + // запрос закешируем
  142 + $prefix = ImportersPrefix::getDb()->cache( function ($db, $configuration, $row ) {
  143 + return ImportersPrefix::find()->where([ 'importer_id' => self::$importer_id,
  144 + 'brand' => $row['BRAND'] ])->one();
  145 + });
118 146  
  147 + if ($prefix) {
  148 + $row['BRAND'] = str_replace($prefix, "", $row['BRAND']);
  149 + }
  150 + }
119 151 return $row;
  152 + }
  153 +
  154 + public static function convertToBrand($value)
  155 + {
  156 + $res = $value;
  157 + $res = trim(strtoupper($res));
  158 + $res = str_replace("Ä", "A", str_replace("Ö", "O", str_replace("Ü", "U", str_replace("Ë", "E", str_replace("Ò", "O", $res)))));
  159 + $res = str_replace(array('@', '#', '~', '"', "'", "?", "!"), '', $res);
  160 +
  161 + return $res;
  162 + }
  163 +
  164 + public static function convertToString($value)
  165 + {
  166 + $value = parent::convertToString($value);
120 167  
  168 + return str_replace(array('!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '~', '`', '"', "'", ' ', '№', '%', ';', ':', '[', ']', '{', '}', '*', '?', '/', '\'', '|', '.', ',', '<', '>', '\\'), '', $value);
121 169 }
122 170  
123 171  
... ...
common/components/parsers/CustomCsvParser.php
... ... @@ -16,7 +16,7 @@ use yii\base\ErrorException;
16 16  
17 17 class CustomCsvParser extends \yii\multiparser\CsvParser {
18 18  
19   - public $last_line = 10;
  19 + public $last_line = 100;
20 20 //public $hasHeaderRow = true;
21 21 // public $keys = ['first','second', 'third', 'forth', 'fifth'];
22 22 public function setupConverter()
... ...
common/components/parsers/config.php
... ... @@ -17,9 +17,11 @@
17 17 'hasKey' => 1,
18 18 'configuration' => ["string" => 'DESCR',
19 19 "float" => 'PRICE',
  20 + "brand" => 'BRAND',
20 21 "integer" => ['BOX','ADD_BOX'],
21 22 "multiply" => [],
22   - "details" => [] // @todo сделать отдельно конфигурирование валидации
  23 + "details" => [],
  24 + "articul" => []
23 25  
24 26 ]
25 27 ],],
... ...
console/controllers/ParserController.php
... ... @@ -32,8 +32,7 @@ class ParserController extends Controller
32 32 'importer_id' => $importer_id,
33 33 'parser_config' => ['keys' => $keys,
34 34 'converter_conf' =>
35   - ['sign' => $sign,
36   - 'multiplier' => $multiplier],
  35 + [ 'sign' => $sign, 'multiplier' => $multiplier, 'importer_id' => $importer_id ],
37 36 'mode' => 'console']
38 37 ];
39 38 if ($this->parseFileConsole($file_path, $config)) {
... ...
console/migrations/m151013_062829_deletePrefixFunction.php 0 → 100644
  1 +<?php
  2 +
  3 +use yii\db\Schema;
  4 +use yii\db\Migration;
  5 +
  6 +/**
  7 + * Class m151013_062829_deletePrefixFunction
  8 + * добавляем две функции - одна ищет префикс по поставщику и бренду,
  9 + * другая удаляет найденный префикс из переданного артикула
  10 + */
  11 +class m151013_062829_deletePrefixFunction extends Migration
  12 +{
  13 + public function safeUp()
  14 + {
  15 + $find_prefix = <<< SQL
  16 + CREATE FUNCTION FindPrefix(p_importer_id int, p_brand VARCHAR(100)) RETURNS VARCHAR(50)
  17 + BEGIN
  18 + DECLARE _prefix varchar(10);
  19 +
  20 + select prefix into _prefix From w_importers_prefix where importer_id = p_importer_id and brand = p_brand COLLATE utf8_general_ci;
  21 +
  22 + RETURN (_prefix);
  23 + END
  24 +SQL;
  25 +
  26 + $delete_prefix = <<< SQL
  27 + CREATE FUNCTION DeletePrefix(p_articul VARCHAR(150), p_importer_id int, p_brand VARCHAR(100)) RETURNS VARCHAR(150)
  28 + BEGIN
  29 + DECLARE _articul varchar(10);
  30 +
  31 + select substring(p_articul, LENGTH( FindPrefix( p_importer_id, p_brand ) ) + 1 ) into _articul;
  32 + RETURN (_articul);
  33 + END
  34 +SQL;
  35 +
  36 + $this->execute($find_prefix);
  37 + $this->execute($delete_prefix);
  38 +
  39 + }
  40 +
  41 + public function safedown()
  42 + {
  43 +
  44 + $find_prefix = <<< SQL
  45 + drop FUNCTION FindPrefix;
  46 +SQL;
  47 +
  48 + $delete_prefix = <<< SQL
  49 + drop FUNCTION DeletePrefix;
  50 +SQL;
  51 +
  52 + $this->execute($find_prefix);
  53 + $this->execute($delete_prefix);
  54 +
  55 +
  56 + }
  57 +
  58 +
  59 +}
... ...