diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..da2fd6f --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,2 @@ +/temp +/uploads \ No newline at end of file diff --git a/backend/components/base/CustomDbConnection.php b/backend/components/base/CustomDbConnection.php new file mode 100644 index 0000000..7de66c6 --- /dev/null +++ b/backend/components/base/CustomDbConnection.php @@ -0,0 +1,37 @@ +getOffset() / 60; + $sgn = ($mins < 0 ? -1 : 1); + $mins = abs($mins); + $hrs = floor($mins / 60); + $mins -= $hrs * 60; + $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); + + $this->pdo->exec("SET time_zone='$offset';"); + + } + + protected function initConnection() + { + parent::initConnection(); + $this->afterOpen(); + + } + + +} \ No newline at end of file diff --git a/backend/components/parsers/CustomConverter.php b/backend/components/parsers/CustomConverter.php new file mode 100644 index 0000000..599a796 --- /dev/null +++ b/backend/components/parsers/CustomConverter.php @@ -0,0 +1,63 @@ + $sub_value) { + if (isset($key_array[$key])) { + // если такой ключ в базовом массиве (массиве ключей) есть, то заменим новым, иначе просто удалим + $new_key = $key_array[$key]; + if( !array_key_exists( $new_key , $res ) ){ + $res[ $new_key ] = $res[$key]; + } + } + unset( $res[$key] ); + } + + return $res; + }, + $value_arr, $key_array); + return $result; + } + + /** + * @param $value_arr - двумерный массив к которому нужно добавить колонки + * @param $add_array - массив с колонками (ключи) и занчениями колонок + * @return mixed + */ + public function addColumns ( array $value_arr , array $add_array ) + { + $i = 0; + while ($i < count($value_arr)) { + foreach ($add_array as $add_key => $add_value) { + $value_arr[$i][$add_key] = $add_value; + } + $i++; + } + return $value_arr; + } +} \ No newline at end of file diff --git a/backend/components/parsers/CustomCsvParser.php b/backend/components/parsers/CustomCsvParser.php index 5674511..dd28725 100644 --- a/backend/components/parsers/CustomCsvParser.php +++ b/backend/components/parsers/CustomCsvParser.php @@ -16,15 +16,27 @@ class CustomCsvParser extends \yii\multiparser\CsvParser { // public $keys = ['first','second', 'third', 'forth', 'fifth']; public function setupConverter() { + if (!count($this->converter_conf)) { + if ($this->hasHeaderRow) { + // если у файла есть заголовок, то в результате имеем ассоциативный массив + $this->converter_conf['hasKey'] = 1; + } - if ($this->hasHeaderRow) { - // если у файла есть заголовок, то в результате имеем ассоциативный массив - $this->converter_conf['hasKey'] = 1; } - $this->converter = \Yii::createObject($this->converter_conf); - } + /** + * @param $arr + * @return mixed + * преобразовует значения прочитанного массива в нужные типы, согласно конфигурации конвертера + */ + protected function convert($arr) + { + $result = \Yii::$app->multiparser->convertByConfiguration( $arr, $this->converter_conf ); + + return $result; + + } } \ No newline at end of file diff --git a/backend/components/parsers/config.php b/backend/components/parsers/config.php index 6e14618..78f141e 100644 --- a/backend/components/parsers/config.php +++ b/backend/components/parsers/config.php @@ -1,14 +1,16 @@ + ['ini' => ['upload_max_filesize' => '20M', + 'post_max_size integer' => '30M', + ]], 'csv' => ['web' => ['class' => 'backend\components\parsers\CustomCsvParser', 'auto_detect_first_line' => true, - 'converter_conf' => ['class' => 'yii\multiparser\Converter', - 'configuration' => [ - "string" => 'DESCR' - ] - ,]], + 'converter_conf' => ['class' => ' backend\components\parsers\CustomConverter', + 'configuration' => ["string" => 'DESCR'],] + ], 'basic_column' => [ Null => 'Пусто', diff --git a/backend/config/main.php b/backend/config/main.php index 386e300..390ddbc 100644 --- a/backend/config/main.php +++ b/backend/config/main.php @@ -43,6 +43,9 @@ return [ 'class' => 'yii\multiparser\YiiMultiparser', 'configuration' => $mp_configuration, + 'as behavior' => [ + 'class' => 'backend\components\parsers\CustomConverter', + ], ], ], diff --git a/backend/controllers/CheckPriceController.php b/backend/controllers/CheckPriceController.php new file mode 100644 index 0000000..59e07c9 --- /dev/null +++ b/backend/controllers/CheckPriceController.php @@ -0,0 +1,99 @@ + [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'actions' => ['index', 'view'], + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], +// 'verbs' => [ +// 'class' => VerbFilter::className(), +// 'actions' => [ +// 'logout' => ['post'], +// ], +// ], + ]; + } + + /** + * @inheritdoc + */ + public function actions() + { + return [ + 'error' => [ + 'class' => 'yii\web\ErrorAction', + ], + ]; + } + + + public function actionIndex() + { + if(Yii::$app->request->isAjax){ + CustomVarDamp::dumpAndDie(1); + } + + //$query = (new Query())->select('*')->from('{{%importer_files}}')->where(['not', ['time_end' => null]])->orderBy(['upload_time' => SORT_DESC]); + $query = Importer::find()->where(['active' => true])->orderBy(['price_date_update' => SORT_DESC]); + + $provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 10, + ], + ]); + return $this->render('index', + [ + 'dataProvider' => $provider, + ]); + } + + + public function actionView ($id) + { + // @todo переписать запрос - нужно условие на равенство даты, а также вьюшка должна быть модальным окном вызываемой по аджаксу + $query = Details::find()->where(['IMPORT_ID' => $id])->orderBy(['timestamp' => SORT_DESC]); + + $provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 16, + ], + ]); + return $this->render('view', + ['dataProvider' => $provider]); + } + +} diff --git a/backend/controllers/Check_priceController.php b/backend/controllers/Check_priceController.php new file mode 100644 index 0000000..4ebd29b --- /dev/null +++ b/backend/controllers/Check_priceController.php @@ -0,0 +1,108 @@ + [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'actions' => ['index', 'view'], + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], +// 'verbs' => [ +// 'class' => VerbFilter::className(), +// 'actions' => [ +// 'logout' => ['post'], +// ], +// ], + ]; + } + + /** + * @inheritdoc + */ + public function actions() + { + return [ + 'error' => [ + 'class' => 'yii\web\ErrorAction', + ], + ]; + } + + + public function actionIndex() + { + + if(Yii::$app->request->isAjax){ + CustomVarDamp::dumpAndDie(1); + } + + //$query = (new Query())->select('*')->from('{{%importer_files}}')->where(['not', ['time_end' => null]])->orderBy(['upload_time' => SORT_DESC]); + $query = Importer::find()->where(['active' => true])->orderBy(['price_date_update' => SORT_DESC]); + + $provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 10, + ], + ]); + return $this->render('index', + [ + 'dataProvider' => $provider, + ]); + } + + + public function actionView ($id) + { + + + + + $query = Details::find()->where(['IMPORT_ID' => $id])->orderBy(['timestamp' => SORT_DESC]); + + $provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 16, + ], + ]); + return $this->render('view', + ['dataProvider' => $provider]); + } +} diff --git a/backend/controllers/ParserController.php b/backend/controllers/ParserController.php index eb6dab9..ec83cb5 100644 --- a/backend/controllers/ParserController.php +++ b/backend/controllers/ParserController.php @@ -2,6 +2,7 @@ namespace backend\controllers; use Yii; +use yii\data\ActiveDataProvider; use yii\filters\AccessControl; use backend\components\base\BaseController; use yii\filters\VerbFilter; @@ -10,16 +11,21 @@ use yii\web\UploadedFile; use yii\data\ArrayDataProvider; use yii\multiparser\DynamicFormHelper; use backend\components\parsers\CustomParserConfigurator; +use backend\models\Details; +use backend\models\ImporterFiles; +use backend\models\Importer; +use yii\base\ErrorException; +use yii\db\Query; use common\components\CustomVarDamp; /** * Parser controller */ - class ParserController extends BaseController { public $layout = "/column"; + /** * @inheritdoc */ @@ -30,7 +36,6 @@ class ParserController extends BaseController 'class' => AccessControl::className(), 'rules' => [ [ - 'actions' => ['index','results','write'], 'allow' => true, 'roles' => ['@'], ], @@ -58,34 +63,84 @@ class ParserController extends BaseController } - - public function actionIndex() + public function actionIndex($mode = 0) { $model = new UploadFileParsingForm(); - + // установим режим, 0 - ручная загрузка, 1 - автозагрузка + $model->mode = $mode; + //CustomVarDamp::dumpAndDie(phpinfo()); return $this->render('index', ['model' => $model]); } - public function actionResults(){ +// public function beforeAction($action) +// { +// if($action->actionMethod ='actionResults'){ +// CustomVarDamp::dumpAndDie(phpinfo()); +// } +// } - $model = new UploadFileParsingForm(); + public function actionResults($mode = 0) + { + $model = new UploadFileParsingForm(['mode' => $mode]); $data = []; if ($model->load(Yii::$app->request->post())) { $model->file = UploadedFile::getInstance($model, 'file'); - + // первый проход - валидируем, сохраняем файл, ложим в кеш (для ручной загрузки) отпарсенные данные и параметры модели (потом при записи в базу данных они пригодятся) if ($model->validate()) { - $filePath = Yii::getAlias('@webroot') . '/uploads/' . $model->file->baseName . '.' . $model->file->extension; + // запишем дату загрузки файла в таблицу файлов поставщика (ImportersFiles) + $files_model = new ImporterFiles(); + // id поставщика получим из конфигурации + $files_model->load(['ImporterFiles' => $model->toArray()]); + try { + $files_model->save(); + } catch (ErrorException $e) { + CustomVarDamp::dump($e->getMessage()); + } + // получим id только что записанной записи - его запишем в название файла + $model->record_id = $files_model->find() + ->where(['importer_id' => $files_model->importer_id]) + ->orderBy(['id' => SORT_DESC]) + ->one() + ->id; - $model->file->saveAs( $filePath ); - $data = $model->readFile($filePath); + $file_name = $model->record_id . '.' . $model->file->extension; - Yii::$app->getCache()->set( 'parser_data', json_encode($data) ); + if ($model->mode) { + $model->file_path = Yii::getAlias('@auto_upload') . '/' . $file_name; + } else { + $model->file_path = Yii::getAlias('@manual_upload') . '/' . $file_name; + } - } + $model->file->saveAs($model->file_path); - } else if( Yii::$app->getCache()->get( 'parser_data' )) { + // для авто загрузки, обработка завершена + if ($model->mode) { + $model->success = true; + return $this->render('index', ['model' => $model]); + } - $data = json_decode( Yii::$app->getCache()->get( 'parser_data' ),true ); + // === ручная загрузка =========== + //запускаем парсинг + $data = $model->readFile(); + // сохраняем в кеш отпарсенные даные + Yii::$app->getCache()->set('parser_data', json_encode($data)); + // сохраняем в кеш модель - в ней настройки для дальнейшей обработки данных + Yii::$app->getCache()->set('parser_configuration', serialize($model)); + + + } else { + // не прошла валидация форма загрузки файлов + //@todo - отправка на страницу ошибок + $errors_arr = $model->getErrors(); + foreach ($errors_arr as $error) { + CustomVarDamp::dump(array_values($error)); + } + die; + } + // листаем пагинатором, или повторно вызываем - считываем из кеша отпрасенные данные + } else if (Yii::$app->getCache()->get('parser_data')) { + + $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); } @@ -96,38 +151,168 @@ class ParserController extends BaseController ], ]); - // CustomVarDamp::dumpAndDie($data); - $header_model = DynamicFormHelper::CreateDynamicModel( count( $data[0] ) ); + //формируем заголовок для пользователя, где он сможет выбрать соответсвие полей (выпадающий список) + $header_model = DynamicFormHelper::CreateDynamicModel(count($data[0])); - // CustomVarDamp::dumpAndDie(Yii::$app->multiparser->getConfiguration('csv','basic_column')); return $this->render('results', ['model' => $data, 'header_model' => $header_model, - 'basic_column' => Yii::$app->multiparser->getConfiguration('csv','basic_column'), + // список колонок для выбора + 'basic_column' => Yii::$app->multiparser->getConfiguration('csv', 'basic_column'), 'dataProvider' => $provider]); } -public function actionWrite() -{ - //CustomVarDamp::dumpAndDie(Yii::$app->request->post()); + public function actionWrite() + { + //получим колонки которые выбрал пользователь + $arr_attributes = Yii::$app->request->post()['DynamicModel']; + //соберем модель по полученным данным + $model = DynamicFormHelper::CreateDynamicModel($arr_attributes); + //добавим правила валидации (колонки должны быть те что указаны в конфиге) + foreach ($arr_attributes as $key => $value) { + $model->addRule($key, 'in', ['range' => array_keys(Yii::$app->multiparser->getConfiguration('csv', 'basic_column'))]); + } + + // провалидируем выбранные колонки + if ($model->validate()) { + + // валидация успешна у нас есть соответсвие колонок, преобразуем в массив данное соответсвие для дальнейшей работы + $arr = $model->toArray(); + + // получим данные из кеша + if (Yii::$app->getCache()->get('parser_data') && Yii::$app->getCache()->get('parser_configuration')) { + $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); + $configuration = unserialize(Yii::$app->getCache()->get('parser_configuration')); + } else { + CustomVarDamp::dumpAndDie('Ошибка кеша'); + } + + // соотнесем отпарсенные данные с соответсивем полученным от пользователя + // для этого преобразуем массив отпарсенных данных - назначим ключи согласно соответствию + $data = \Yii::$app->multiparser->convertToAssocArray($data, $arr, 'attr_'); + + + // 1. запишем дату старта в таблицу файлов поставщика (ImportersFiles) + // id загруженного файла получим из конфигурации + $files_model = ImporterFiles::findOne( $configuration->record_id ); + + //$files_model->load(['ImporterFiles' => $configuration->toArray()]); + $update_date = date('Y-m-d H:i:s'); + $files_model->time_start = $update_date; + // запишем дату начала загрузки + if (!$files_model->save()) { + CustomVarDamp::dumpAndDie($files_model->getErrors()); + } + + + // 2. запишем полученные данные в таблицу товаров (Details) + $details_model = new Details(); + // проверим все ли обязательные колонки были указаны пользователем + $details_model->load(['Details' => $data[0]]); + if ($details_model->validate()) { + // дополним данные значением импортера и даты обновления цены + $data = \Yii::$app->multiparser->addColumns($data, ['IMPORT_ID' => $configuration->importer_id, 'timestamp' => $update_date]); + + try { + //@todo add transaction + // попытаемся вставить данные в БД с апдейтом по ключам + $details_model->ManualInsert($data); + + // 3. зафиксируем дату конца загрузки в файлах поставщика + + $files_model->time_end = date('Y-m-d H:i:s'); + // CustomVarDamp::dumpAndDie($files_model); + if (!$files_model->save()) { + CustomVarDamp::dumpAndDie($files_model->getErrors()); + } + + // 4. зафиксируем дату загрузки в таблице поставщиков + $imp_model = Importer::findOne($configuration['importer_id']); + $imp_model->price_date_update = $update_date; + + if (!$imp_model->save()) { + CustomVarDamp::dumpAndDie($imp_model->getErrors()); + } + $configuration['success'] = true; + // все прошло успешно - очищаем кеш + Yii::$app->getCache()->delete('parser_data'); + Yii::$app->getCache()->delete('parser_configuration'); + + unlink($configuration['file_path']); + return $this->render('index', ['model' => $configuration]); + + } catch (ErrorException $e) { + CustomVarDamp::dump($e->getMessage()); + } + } + if ($details_model->hasErrors()) { + $errors_arr = $details_model->getErrors(); + foreach ($errors_arr as $error) { + CustomVarDamp::dump(array_values($error)); + } + + } + + + } - $arr_attributes = Yii::$app->request->post()['DynamicModel']; - $model = DynamicFormHelper::CreateDynamicModel( $arr_attributes ); - foreach ($arr_attributes as $key => $value) { - $model->addRule($key, 'in', ['range' => array_keys( Yii::$app->multiparser->getConfiguration('csv','basic_column') )]); } - //CustomVarDamp::dumpAndDie($model); - if ($model->validate()) { - $arr = $model->toArray(); - $data = json_decode( Yii::$app->getCache()->get( 'parser_data' ),true ); + public function actionAutoUpload() + { + $query = Importer::find()->where(['active' => true])->orderBy(['price_date_update' => SORT_DESC]); - // CustomVarDamp::dumpAndDie(DynamicFormHelper::CreateAssocArray($data, $arr)); - CustomVarDamp::dumpAndDie($arr); + $provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 10, + ], + ]); + return $this->render('check_price', + [ + 'dataProvider' => $provider]); } + public function actionServerFiles () + { + $arr_id = []; + // получим список файлов которые ожидают к загрузке + foreach (glob(Yii::getAlias('@auto_upload') . '/*') as $server_file) { + $file_id = basename($server_file,".csv"); + $arr_id[] = (int) $file_id; + } + $query = ImporterFiles::find()->where(['in', 'id', $arr_id])->orderBy(['upload_time' => SORT_DESC]); -} + $provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 10, + ], + ]); + return $this->render('server-files', + [ + 'dataProvider' => $provider]); + } + + public function actionDelete ($id) + { + if(Yii::$app->request->isAjax){ + CustomVarDamp::dumpAndDie(1); + } + + $files_model = new ImporterFiles(); + try { + + $files_model->delete($id); + unlink(Yii::getAlias('@auto_upload') . '/' . $id . '.csv' ); + + } catch (ErrorException $e) { + CustomVarDamp::dump($e->getMessage()); + + } + + $this->redirect('server-files'); + } } diff --git a/backend/models/Details.php b/backend/models/Details.php new file mode 100644 index 0000000..38c3e07 --- /dev/null +++ b/backend/models/Details.php @@ -0,0 +1,102 @@ + 100], + [['FULL_ARTICLE'], 'string', 'max' => 150], + [['DESCR', 'GROUP'], 'string', 'max' => 200] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'ID' => Yii::t('app', 'ID'), + 'IMPORT_ID' => Yii::t('app', 'Import ID'), + 'BRAND' => Yii::t('app', 'Brand'), + 'ARTICLE' => Yii::t('app', 'Article'), + 'FULL_ARTICLE' => Yii::t('app', 'Full Article'), + 'PRICE' => Yii::t('app', 'Price'), + 'DESCR' => Yii::t('app', 'Descr'), + 'BOX' => Yii::t('app', 'Box'), + 'ADD_BOX' => Yii::t('app', 'Add Box'), + 'GROUP' => Yii::t('app', 'Group'), + 'timestamp' => Yii::t('app', 'Timestamp'), + ]; + } + + public function ManualInsert ($data) + { + // \common\components\CustomVarDamp::dumpAndDie($data); + $table_name = self::tableName(); + $keys_arr = array_keys( $data[0] ); + // найдем те поля которые не являются ключами. Их нужно будет при дубляже апдейтить + $fields_arr_to_update = array_diff( $keys_arr, $this::KEY_COLUMN ); + + $query_update = ' on duplicate key update '; + foreach ($fields_arr_to_update as $field) { + $query_update .= "{$field} = values({$field}),"; + } + // удалим последнюю запятую + $query_update = substr($query_update, 0, strlen($query_update) - 1); + + // запросы будем выполнять пакетами + // размер пакета установлен в константе + // разобъем массив на пакеты и будем их проходить + $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; + // добавим фрагмент с апдейтом при дубляже + $query = "{$query_insert} {$query_update}"; + // \common\components\CustomVarDamp::dumpAndDie($query); + $res = Yii::$app->db->createCommand($query)->execute(); + + } + + } +} diff --git a/backend/models/Details_old.php b/backend/models/Details_old.php new file mode 100644 index 0000000..15b1f67 --- /dev/null +++ b/backend/models/Details_old.php @@ -0,0 +1,135 @@ +mode = $mode; + } + + public function rules() + { + return [ + [['BRAND','ARTICLE', 'PRICE', 'BOX'], 'required' ], + ]; + } + + public function formName() + { + return 'Details'; + } + + + public static function tableName() + { + return '{{%details}}'; + } + +// //@todo вероятно этой функции не место здесь +// public function prepareData ( $data, $configuration ) +// { +// if ( isset($configuration['importer_id']) && $configuration['importer_id']) { +// $data = \Yii::$app->multiparser->addColumn( $data, 'IMPORT_ID', $configuration['importer_id'] ); +// } +// // \common\components\CustomVarDamp::dumpAndDie($data); +// return $data; +// } + + /** + * @param $data - двумерный массив данных для записи в таблицу details + * @throws \yii\db\Exception + * вставляет записи с апдейтом при дубляже ключей + */ + public function save ($data) + { + $table_name = self::tableName(); + $keys_arr = array_keys( $data[0] ); + // найдем те поля которые не являются ключами. Их нужно будет при дубляже апдейтить + $fields_arr_to_update = array_diff( $keys_arr, $this::KEY_COLUMN ); + + $query_update = ' on duplicate key update '; + foreach ($fields_arr_to_update as $field) { + $query_update .= "{$field} = values({$field}),"; + } + // удалим последнюю запятую + $query_update = substr($query_update, 0, strlen($query_update) - 1); + + // запросы будем выполнять пакетами + // размер пакета установлен в константе + // разобъем массив на пакеты и будем их проходить + $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; + // добавим фрагмент с апдейтом при дубляже + $query = "{$query_insert} {$query_update}"; + // \common\components\CustomVarDamp::dumpAndDie($query); + $res = Yii::$app->db->createCommand($query)->execute(); + + } + + } +} + +// + +//$q = " INSERT INTO {$table_name} ({$keys_string}) VALUES ("; + +//$q .= " on duplicate key update `FULL_ARTICLE` = values (`FULL_ARTICLE`), +// `PRICE` = values (`PRICE`), +// `DESCR` = values(`DESCR`), +// `BOX` = values(`BOX`), +// `ADD_BOX` = values(`ADD_BOX`), +// `GROUP` = values(`GROUP`);"; + +// INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6) +// ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b); + + + +//INSERT INTO `books` (`UserId`, `BookId`, `Count`) VALUES (13, 1001, 3) +//ON DUPLICATE KEY UPDATE `Count` = `Count` + VALUES(`Count`); + +//$values_string = ''; +//$keys_arr = array_keys( $data[0] ); +//$keys_string = implode( ',', $keys_arr); +//$table_name = self::tableName(); +//$current_batch = 0; +//for ($i = $current_batch; $i < $this::BATCH AND $i < count($data); $i++) { +// $values_string .= '(' . implode( ',', $data[$i]) . '),'; +//} +// for ($current_batch = $this::BATCH; $current_batchdb->createCommand()->batchInsert($table_name, $keys_arr, $data)->sql; +//$query = "{$query_insert} on duplicate key update `PRICE` = values (`PRICE`),`DESCR` = values(`DESCR`),`BOX` = values(`BOX`)"; +//$res = Yii::$app->db->createCommand($query)->execute(); + + + +// Yii::$app->db->createCommand()->batchInsert($table_name, $keys_arr, $data)->sql execute(); \ No newline at end of file diff --git a/backend/models/Importer.php b/backend/models/Importer.php index daaa435..8c06694 100644 --- a/backend/models/Importer.php +++ b/backend/models/Importer.php @@ -47,13 +47,14 @@ class Importer extends BaseActiveRecord public function rules() { return [ - [['code', 'name', 'name_price', 'currency_id', 'delivery', 'email', 'info', 'PARSER_FIELD_SIGN', 'price_date_update'], 'required'], + [['code', 'name', 'currency_id', 'delivery', 'price_date_update'], 'required'], + [['name_price', 'email', 'PARSER_FIELD_SIGN', 'info'], 'safe'], [['currency_id', 'active', 'PARSER_IS_ACTIVE', 'PARSER_COLUMN_COUNT', 'PARSER_FIELD_BRAND', 'PARSER_FIELD_ARTICLE', 'PARSER_FIELD_ARTICLE_PREFIX', 'PARSER_FIELD_PRICE', 'PARSER_FIELD_DESCR', 'PARSER_FIELD_BOX', 'PARSER_FIELD_ADD_BOX', 'PARSER_FIELD_GROUP_RG'], 'integer'], [['info'], 'string'], [['PARSER_FIELD_MULTIPLIER'], 'number'], [['code', 'name', 'name_price', 'delivery', 'email'], 'string', 'max' => 254], [['PARSER_FIELD_SIGN'], 'string', 'max' => 1], - [['price_date_update'], 'string', 'max' => 15], + // [['price_date_update'], 'string', 'max' => 15], [['code'], 'unique'], [['name'], 'unique'] ]; diff --git a/backend/models/ImporterFiles.php b/backend/models/ImporterFiles.php new file mode 100644 index 0000000..ab664ab --- /dev/null +++ b/backend/models/ImporterFiles.php @@ -0,0 +1,57 @@ +hasOne(Importer::className(), ['id' => 'importer_id'])->one()->name; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'importer_id' => Yii::t('app', 'Importer ID'), + 'upload_time' => Yii::t('app', 'Upload Time'), + 'time_start' => Yii::t('app', 'Time Start'), + 'time_end' => Yii::t('app', 'Time End'), + ]; + } +} diff --git a/backend/models/UploadFileParsingForm.php b/backend/models/UploadFileParsingForm.php index ef94bc4..3f8eb10 100644 --- a/backend/models/UploadFileParsingForm.php +++ b/backend/models/UploadFileParsingForm.php @@ -14,28 +14,48 @@ class UploadFileParsingForm extends Model /** * @var UploadedFile file attribute */ + // атрибуты формы public $file; - public $importer; + public $importer_id; public $action; public $delimiter; public $delete_price; public $delete_prefix; + // служебные атрибуты + public $file_path; + public $success; + public $mode; //0 - режим ручной загрузки, 1 - режим автозагрузки + public $record_id; // id таблицы в которую записывается информация о файле + /** * @return array the validation rules. */ + public function __construct($config = []) + { + parent::__construct($config); + if ( $this->mode ) { + // автозагрузка, проставим сценарий + $this->scenario = 'auto'; + } + + } + + public function rules() { return [ - ['importer', 'required', 'message' => 'Не указан поставщик!' ], + ['importer_id', 'required', 'message' => 'Не указан поставщик!' ], ['file', 'required', 'message' => 'Не выбран файл!' ], //@todo - not working this file validator!!! - fixed [['file'], 'file'],// 'extensions' => ['csv', 'xml'] ], // 'wrongMimeType' => 'Указан неподдерживаемый тип файла. Можно выбирать csv, xml файлы.' ], - ['importer', 'integer','max' => 999999, 'min' => 0 ], - [['action','delete_prefix', 'delete_price'], 'boolean'], + ['importer_id', 'integer','max' => 999999, 'min' => 0 ], + [['action','delete_prefix', 'delete_price', 'success'], 'boolean', 'except' => 'auto' ], // только для ручной загрузки ['delimiter', 'string', 'max' => 1], - ['delimiter', 'default', 'value' => ';'] + [['mode','record_id'], 'safe'], + ['delimiter', 'default', 'value' => ';'], + [ 'success', 'default', 'value' => false] ]; } @@ -44,17 +64,34 @@ class UploadFileParsingForm extends Model { return [ 'file' => Yii::t('app', 'Источник'), - 'importer' => Yii::t('app', 'Поставщик'), + 'importer_id' => Yii::t('app', 'Поставщик'), 'delimiter' => Yii::t('app', 'Разделитель'), ]; } - public function readFile($filePath){ + public function readFile(){ - $data = Yii::$app->multiparser->parse($filePath); + $data = Yii::$app->multiparser->parse( $this->file_path ); if( !is_array($data) ){ $data = ['No results']; } + return $data; } + + public function fields() + { + return [ + + 'importer_id', + 'delimiter', + 'delete_price', + 'delete_prefix', + 'file_path', + // id записи таблицы ImportersFiles, + // 'id' => 'record_id', + ]; + } + + } \ No newline at end of file diff --git a/backend/views/check-price/index.php b/backend/views/check-price/index.php new file mode 100644 index 0000000..e34f765 --- /dev/null +++ b/backend/views/check-price/index.php @@ -0,0 +1,58 @@ +title = 'Проверка прайсов'; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

title) ?>

+ + + $dataProvider, + 'columns' => [['class' => SerialColumn::className()], + [ + 'class' => ActionColumn::className(), + 'template'=>'{view}', + 'contentOptions' => function ($model, $key, $index, $column){ + return ['data' => ['id' => $model->id, 'date' => $model->price_date_update]]; + } + ], + [ + 'label' =>'Поставщик', + 'value' => function ($data) { + return '№ ' .$data->id . ' ' . $data->name; + }, + ], + ['label' =>'Дата обновления', + 'attribute' => 'price_date_update' ], + ['label' => 'Кол-во дней', + 'value' => function ($data) { + $date1 = new DateTime("now"); + $date2 = new DateTime( $data->price_date_update ); + $quo_days = $date2->diff($date1)->format('%R%a'); + // уберем первый символ - там знак "+" + $quo_days = substr( $quo_days, 1, strlen($quo_days) ); + $quo_days = (int) $quo_days; + + if($quo_days > 15) + $quo_days = '>15'; + + return $quo_days; + } + ], + ]] );?> + + + + +
\ No newline at end of file diff --git a/backend/views/check-price/view.php b/backend/views/check-price/view.php new file mode 100644 index 0000000..f6d9ff0 --- /dev/null +++ b/backend/views/check-price/view.php @@ -0,0 +1,32 @@ +title = 'Проверка прайсов'; +$this->params['breadcrumbs'][] = $this->title; + +?> +
+ +

title) ?>

+ + $dataProvider, + + ] ); + + + ?> + + + +
+ \ No newline at end of file diff --git a/backend/views/check_price/index.php b/backend/views/check_price/index.php new file mode 100644 index 0000000..e34f765 --- /dev/null +++ b/backend/views/check_price/index.php @@ -0,0 +1,58 @@ +title = 'Проверка прайсов'; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

title) ?>

+ + + $dataProvider, + 'columns' => [['class' => SerialColumn::className()], + [ + 'class' => ActionColumn::className(), + 'template'=>'{view}', + 'contentOptions' => function ($model, $key, $index, $column){ + return ['data' => ['id' => $model->id, 'date' => $model->price_date_update]]; + } + ], + [ + 'label' =>'Поставщик', + 'value' => function ($data) { + return '№ ' .$data->id . ' ' . $data->name; + }, + ], + ['label' =>'Дата обновления', + 'attribute' => 'price_date_update' ], + ['label' => 'Кол-во дней', + 'value' => function ($data) { + $date1 = new DateTime("now"); + $date2 = new DateTime( $data->price_date_update ); + $quo_days = $date2->diff($date1)->format('%R%a'); + // уберем первый символ - там знак "+" + $quo_days = substr( $quo_days, 1, strlen($quo_days) ); + $quo_days = (int) $quo_days; + + if($quo_days > 15) + $quo_days = '>15'; + + return $quo_days; + } + ], + ]] );?> + + + + +
\ No newline at end of file diff --git a/backend/views/check_price/view.php b/backend/views/check_price/view.php new file mode 100644 index 0000000..f6d9ff0 --- /dev/null +++ b/backend/views/check_price/view.php @@ -0,0 +1,32 @@ +title = 'Проверка прайсов'; +$this->params['breadcrumbs'][] = $this->title; + +?> +
+ +

title) ?>

+ + $dataProvider, + + ] ); + + + ?> + + + +
+ \ No newline at end of file diff --git a/backend/views/layouts/column.php b/backend/views/layouts/column.php index 62aa1be..2e9e118 100644 --- a/backend/views/layouts/column.php +++ b/backend/views/layouts/column.php @@ -282,7 +282,13 @@ $this->beginContent('@app/views/layouts/main.php'); echo Menu::widget([ 'options' => ['class' => 'sidebar-menu'], 'items' => [ - ['label' => "Прайс парсер", 'url' => ['parser/index']], + ['label' => "Загрузка файлов", 'url' => ['#'], 'items' => [ + ['label' => 'Файлы на сервере', 'url' => ['parser/server-files']], + ['label' => 'Загрузить файл на сервер', 'url' => ['parser/index', 'mode' => 1]], + ['label' => 'Ручная загрузка', 'url' => ['parser/index']], + ['label' => 'Проверка прайс файлов', 'url' => ['check-price/index']], + ], + ], ['label' => 'Управление ролями', 'url' => ['#'], 'items' => [ ['label' => 'Покупатели', 'url' => '#'], ['label' => 'Поставщики', 'url' => '#'], diff --git a/backend/views/parser/index.php b/backend/views/parser/index.php index b630fe8..a7c2646 100644 --- a/backend/views/parser/index.php +++ b/backend/views/parser/index.php @@ -3,29 +3,50 @@ use yii\widgets\ActiveForm; use yii\helpers\Html; use backend\models\Importer; use yii\helpers\ArrayHelper; +if ( $model->mode ) { + // авто загрузка + $mode = 1; + $button_label = 'Загрузить'; +} else { + // ручная загрузка + $mode = 0; + $button_label = 'Прочитать'; +} ?>
- ['enctype' => 'multipart/form-data',],'action'=>['parser/results']]); + ['enctype' => 'multipart/form-data',],'action'=>['parser/results', 'mode' => $mode]]); if (!$model->action) { $model->action = 1; } + if ($model->success) { // вернулись после успешной загрузки данного файла + echo Html::tag('h3', 'Файл успешно загружен',['class'=>'bg-success']); + } ?>

Загрузка прайсов поставщиков

- field($model, 'importer')->dropDownList(ArrayHelper::map( Importer::find()->all(), 'id','name' )); ?> - field($model, 'delete_price')->checkbox(['label' => 'Загрузить с удалением старого прайса']) ?> + field($model, 'importer_id')->dropDownList(ArrayHelper::map( Importer::find()->all(), 'id','name' )); ?> + + field($model, 'delete_price')->checkbox(['label' => 'Загрузить с удалением старого прайса']); + } + ?> + field($model, 'file')->fileInput()->label(false) ?> - field($model, 'action')->radioList([1 => 'Стандартная обработка', 0 => 'С разделителем'])->label(false) ?> - field($model, 'delimiter', ['inputOptions' => ['value' => ';']]) ?> - field($model, 'delete_prefix')->checkbox(['label' => 'Удалять префикс']) ?> + field($model, 'action')->radioList([1 => 'Стандартная обработка', 0 => 'С разделителем'])->label(false); + echo $form->field($model, 'delimiter', ['inputOptions' => ['value' => ';']]); + + echo $form->field($model, 'delete_prefix')->checkbox(['label' => 'Удалять префикс']); + } + ?>
- 'btn btn-primary']) ?> + 'btn btn-primary']) ?>
diff --git a/backend/views/parser/server-files.php b/backend/views/parser/server-files.php new file mode 100644 index 0000000..e82bc0a --- /dev/null +++ b/backend/views/parser/server-files.php @@ -0,0 +1,54 @@ +title = 'Проверка прайсов'; +$this->params['breadcrumbs'][] = $this->title; +Pjax::begin(); + +?> +
+ +

title) ?>

+ + $dataProvider, + 'columns' => [['class' => SerialColumn::className()], + [ + 'label' =>'Поставщик', + 'value' => function ($data) { + return $data->importer; + }, + ], + ['label' =>'Дата загрузки', + 'attribute' => 'upload_time' ], + + ['class' => ActionColumn::className(), + 'template'=>'{delete}', + 'buttons' => [ + 'delete' => function ($url, $model, $key) { + return Html::a('', $url, [ + 'title' => Yii::t('yii', 'Удалить файл'), + 'data-confirm' => 'Вы уверены что хотите удалить этот файл?', + 'data-method' => 'post', + 'data-pjax' => '1', + ]); + }, + ], + ] + + ]] );?> + + + +
+ \ No newline at end of file diff --git a/common/config/bootstrap.php b/common/config/bootstrap.php index ecc13e5..df33c86 100644 --- a/common/config/bootstrap.php +++ b/common/config/bootstrap.php @@ -3,3 +3,5 @@ Yii::setAlias('common', dirname(__DIR__)); Yii::setAlias('frontend', dirname(dirname(__DIR__)) . '/frontend'); Yii::setAlias('backend', dirname(dirname(__DIR__)) . '/backend'); Yii::setAlias('console', dirname(dirname(__DIR__)) . '/console'); +Yii::setAlias('auto_upload', dirname(dirname(__DIR__)) . '/backend/uploads/auto'); +Yii::setAlias('manual_upload', dirname(dirname(__DIR__)) . '/backend/uploads/manual'); diff --git a/console/migrations/m150915_125129_addDetails.php b/console/migrations/m150915_125129_addDetails.php new file mode 100644 index 0000000..d8d5419 --- /dev/null +++ b/console/migrations/m150915_125129_addDetails.php @@ -0,0 +1,40 @@ +execute('CREATE TABLE `details` ( + `ID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `IMPORT_ID` int(6) unsigned NOT NULL, + `BRAND` varchar(100) NOT NULL, + `ARTICLE` varchar(100) NOT NULL, + `FULL_ARTICLE` varchar(150) NOT NULL, + `PRICE` float(15,2) unsigned NOT NULL, + `DESCR` varchar(200) NOT NULL, + `BOX` int(6) unsigned NOT NULL, + `ADD_BOX` int(6) unsigned NOT NULL DEFAULT 0, + `GROUP` varchar(200) NOT NULL DEFAULT \'\', + `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`ARTICLE`,`BRAND`,`IMPORT_ID`), + UNIQUE KEY `ID_delete` (`ID`), + KEY `timestamp` (`timestamp`), + KEY `ARTICLE` (`ARTICLE`,`BRAND`,`BOX`), + KEY `BRAND` (`BRAND`,`ARTICLE`), + KEY `ARTICLE_2` (`ARTICLE`,`BRAND`,`ADD_BOX`), + KEY `IMPORT_ID` (`IMPORT_ID`,`ARTICLE`), + KEY `IMPORT_ID_2` (`IMPORT_ID`,`timestamp`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8'); + + } + + public function down() + { + $this->dropTable('{{%details}}'); + + } + +} diff --git a/console/migrations/m150922_094313_change_key_ImportFiles.php b/console/migrations/m150922_094313_change_key_ImportFiles.php new file mode 100644 index 0000000..5606ec2 --- /dev/null +++ b/console/migrations/m150922_094313_change_key_ImportFiles.php @@ -0,0 +1,22 @@ +dropIndex('importer_id', '{{%importer_files}}'); + $this->createIndex('importer_id', '{{%importer_files}}', 'importer_id, upload_time', false); + } + + public function down() + { + $this->dropIndex('importer_id', '{{%importer_files}}'); + $this->createIndex('importer_id', '{{%importer_files}}', 'importer_id, time_start', false); + } + + +} diff --git a/console/migrations/m150922_144040_change_Importer_dataPrice.php b/console/migrations/m150922_144040_change_Importer_dataPrice.php new file mode 100644 index 0000000..f62873d --- /dev/null +++ b/console/migrations/m150922_144040_change_Importer_dataPrice.php @@ -0,0 +1,21 @@ +alterColumn('{{%importer}}','price_date_update','TIMESTAMP' ); + $this->createIndex('price_date', '{{%importer}}', 'price_date_update', false); + } + + public function down() + { + $this->alterColumn('{{%importer}}','price_date','varchar(15)' ); + $this->dropIndex('price_date', '{{%importer}}'); + } + + +} diff --git a/console/migrations/m150925_111922_add_foreign_key_ImportFiles.php b/console/migrations/m150925_111922_add_foreign_key_ImportFiles.php new file mode 100644 index 0000000..10f44aa --- /dev/null +++ b/console/migrations/m150925_111922_add_foreign_key_ImportFiles.php @@ -0,0 +1,18 @@ +addForeignKey('importer_fk', '{{%importer_files}}', 'importer_id', '{{%importer}}', 'id'); + } + + public function down() + { + $this->dropForeignKey('importer_fk', '{{%importer_files}}'); + } + +} -- libgit2 0.21.4