diff --git a/backend/controllers/CrossingUploadController.php b/backend/controllers/CrossingUploadController.php new file mode 100644 index 0000000..cb323c6 --- /dev/null +++ b/backend/controllers/CrossingUploadController.php @@ -0,0 +1,115 @@ + [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'actions' => ['index', 'result'], + 'allow' => true, + 'roles' => ['@'], + ], + ], + ], +// 'verbs' => [ +// 'class' => VerbFilter::className(), +// 'actions' => [ +// 'logout' => ['post'], +// ], +// ], + ]; + } + + /** + * @inheritdoc + */ + public function actions() + { + return [ + 'error' => [ + 'class' => 'yii\web\ErrorAction', + ], + ]; + } + + + public function actionIndex() + { + $model = new UploadFileCrossingForm(); + return $this->render('index', ['model' => $model]); + } + + public function actionResult() + { + $model = new UploadFileCrossingForm(); + $data = []; + if ($model->load(Yii::$app->request->post())) { + $model->file = UploadedFile::getInstance($model, 'file'); + + if ($model->validate()) { + $file_name = $model->file->name; + $model->file_path = Yii::getAlias('@temp_upload') . '/' . $file_name; + + $model->file->saveAs($model->file_path); + //запускаем парсинг + // доп. опции для парсера - удаление префикса в артикулах + $options['mode'] = 'crosses'; + $fields = []; + if ($model->delete_prefix1) { + $fields[] = 'ARTICLE'; + } + if ($model->delete_prefix2) { + $fields[] = 'CROSS_ARTICLE'; + } + if ( $fields ) { + $options [ 'converter_conf' ] = [ 'configuration' => [ "article" => $fields , + "string" => ['ARTICLE', 'CROSS_ARTICLE'],] ]; + } else { + $options [ 'converter_conf' ] = [ 'configuration' => [ "string" => ['ARTICLE', 'CROSS_ARTICLE'], ] ]; + } + + $data = $model->readFile( $options ); + $crosses_model = new DetailsCrosses(); + $crosses_model->ManualInsertWithIgnore( $data ); + + Yii::$app->session->setFlash('success', 'Файл кроссов успешно загружен'); + return $this->render('index', ['model' => $model]); + }else{ + // не прошла валидация форма загрузки файлов + $errors_str = ''; + foreach ($model->getErrors() as $error) { + $errors_str .= implode( array_values($error) ); + } + throw new \ErrorException( $errors_str ); + } + + } else { + throw new \ErrorException( 'Ошибка загрузки данных' ); + } + } +} \ No newline at end of file diff --git a/backend/controllers/ParserController.php b/backend/controllers/ParserController.php index 09ef226..c035606 100644 --- a/backend/controllers/ParserController.php +++ b/backend/controllers/ParserController.php @@ -10,7 +10,6 @@ use backend\models\UploadFileParsingForm; use yii\web\UploadedFile; use yii\data\ArrayDataProvider; use yii\multiparser\DynamicFormHelper; -use backend\components\parsers\CustomParserConfigurator; use backend\models\ImportersFiles; use backend\models\Importers; use yii\base\ErrorException; diff --git a/backend/models/DetailsCrosses.php b/backend/models/DetailsCrosses.php index ed4747c..b10ff57 100644 --- a/backend/models/DetailsCrosses.php +++ b/backend/models/DetailsCrosses.php @@ -2,6 +2,7 @@ namespace backend\models; +use common\components\CustomVarDamp; use Yii; /** @@ -17,6 +18,10 @@ use Yii; class DetailsCrosses extends \backend\components\base\BaseActiveRecord { /** + * int - размер пакета запроса + */ + const BATCH = 1000; + /** * @inheritdoc */ public static function tableName() @@ -50,4 +55,31 @@ class DetailsCrosses extends \backend\components\base\BaseActiveRecord 'timestamp' => Yii::t('app', 'Timestamp'), ]; } + + /** + * вставка данных с игнором дублей прямым запросом SQL + * @param $data - массив вставляемых данный, вставка будет прозводится пакетами размером указанным в константе BATCH + * @throws \yii\db\Exception + */ + //@todo - вынести все ручные инсерты в отдельный класс + public function ManualInsertWithIgnore( $data ) + { + // \common\components\CustomVarDamp::dumpAndDie($data); + $table_name = self::tableName(); + $keys_arr = array_keys($data[0]); + + // запросы будем выполнять пакетами + // размер пакета установлен в константе + // разобъем массив на пакеты и будем их проходить + $data = array_chunk($data, $this::BATCH); + foreach ($data as $current_batch_array) { + + //воспользуемся пакетной вставкой от фреймворка + $query = Yii::$app->db->createCommand()->batchInsert($table_name, $keys_arr, $current_batch_array)->sql; + // добавим ключевое слово - ignore + $query = preg_replace('/INSERT/','INSERT IGNORE', $query); + Yii::$app->db->createCommand($query)->execute(); + + } + } } diff --git a/backend/models/UploadFileCrossingForm.php b/backend/models/UploadFileCrossingForm.php new file mode 100644 index 0000000..6c4239c --- /dev/null +++ b/backend/models/UploadFileCrossingForm.php @@ -0,0 +1,69 @@ + 'Не выбран файл!' ], + [['file'], 'file', 'extensions' => 'csv', 'checkExtensionByMimeType'=>false ], + [['delete_prefix1', 'delete_prefix2'], 'boolean' ], + + ]; + } + + public function attributeLabels() + { + return [ + 'file' => Yii::t('app', 'Источник'), + 'delete_prefix1' => Yii::t('app', 'Удалять префикс в артикуле товара 1'), + 'delete_prefix2' => Yii::t('app', 'Удалять префикс в артикуле товара 2'), + ]; + } + + public function readFile( $options = [] ){ + + $data = Yii::$app->multiparser->parse( $this->file_path, $options ); + if( !is_array( $data ) ){ + throw new ErrorException("Ошибка чтения из файла кроссов {$this->file_path}"); + } + // файл больше не нужен - данные прочитаны и сохранены в кеш + if( file_exists($this->file_path) ) + unlink($this->file_path); + + return $data; + } + + public function fields() + { + return [ + + 'delete_price1', + 'delete_price2' + + ]; + } + + +} \ No newline at end of file diff --git a/backend/models/UploadFileParsingForm.php b/backend/models/UploadFileParsingForm.php index 3dd3d62..c66ab5e 100644 --- a/backend/models/UploadFileParsingForm.php +++ b/backend/models/UploadFileParsingForm.php @@ -48,9 +48,7 @@ class UploadFileParsingForm extends Model return [ ['importer_id', 'required', 'message' => 'Не указан поставщик!' ], ['file', 'required', 'message' => 'Не выбран файл!' ], - //@todo - not working this file validator!!! - fixed - [['file'], 'file'],// 'extensions' => ['csv', 'xml'] ], - // 'wrongMimeType' => 'Указан неподдерживаемый тип файла. Можно выбирать csv, xml файлы.' ], + [['file'], 'file', 'extensions' => ['csv', 'xml'], 'checkExtensionByMimeType'=>false ], ['importer_id', 'integer','max' => 999999, 'min' => 0 ], [['action','delete_prefix', 'delete_price', 'success'], 'boolean', 'except' => 'auto' ], // только для ручной загрузки ['delimiter', 'string', 'max' => 1], diff --git a/backend/views/crossing-upload/index.php b/backend/views/crossing-upload/index.php new file mode 100644 index 0000000..7b8cf4d --- /dev/null +++ b/backend/views/crossing-upload/index.php @@ -0,0 +1,28 @@ + +
+
+ ['enctype' => 'multipart/form-data',],'action'=>['crossing-upload/result']]); + + if ($msg = \Yii::$app->session->getFlash('success')) { // вернулись после успешной загрузки данного файла + echo Html::tag('h3', $msg ,['class'=>'bg-success']); + } + ?> +

Кросс файлы

+ + field($model, 'delete_prefix1')->checkbox() ?> + field($model, 'delete_prefix2')->checkbox() ?> + field($model, 'file')->fileInput()->label(false) ?> + +
+ 'btn btn-primary']) ?> +
+ + +
+
+ diff --git a/backend/views/layouts/column.php b/backend/views/layouts/column.php index 4ab7533..d77c84f 100644 --- a/backend/views/layouts/column.php +++ b/backend/views/layouts/column.php @@ -283,6 +283,7 @@ $this->beginContent('@app/views/layouts/main.php'); 'options' => ['class' => 'sidebar-menu'], 'items' => [ ['label' => "Загрузка файлов", 'url' => ['#'], 'items' => [ + ['label' => 'Кросс-файлы', 'url' => ['crossing-upload/index']], ['label' => 'Файлы на сервере', 'url' => ['parser/server-files']], ['label' => 'Загрузить файл на сервер', 'url' => ['parser/index', 'mode' => 1]], ['label' => 'Ручная загрузка', 'url' => ['parser/index']], diff --git a/common/components/PriceWriter.php b/common/components/PriceWriter.php index ac08e45..ecc88ff 100644 --- a/common/components/PriceWriter.php +++ b/common/components/PriceWriter.php @@ -65,8 +65,15 @@ class PriceWriter $row['PRICE'] = \Yii::$app->multiparser->convertToFloat($row['PRICE']); $row['BOX'] = \Yii::$app->multiparser->convertToInteger($row['BOX']); // присвоим полный артикул + $row['FULL_ARTICLE'] = $row['ARTICLE']; - $row['ARTICLE'] = \Yii::$app->multiparser->convertToArticle( $row ); + if ((int)$this->configuration['delete_prefix']) { + $row = \Yii::$app->multiparser->convertToArticle( $row, $this->configuration['importer_id'] ); + } else { + $row['ARTICLE'] = \Yii::$app->multiparser->convertToArticle( $row['ARTICLE'] ); + } + + if (isset($row['ADD_BOX'])) $row['ADD_BOX'] = \Yii::$app->multiparser->convertToInteger($row['ADD_BOX']); @@ -84,9 +91,6 @@ class PriceWriter try { //@todo add transaction - if ((int)$this->configuration['delete_prefix']) { - $details_model->delete_prefix = true; - } if ((int)$this->configuration['delete_price']) { $details_model->delete_price = true; } diff --git a/common/components/parsers/CustomConverter.php b/common/components/parsers/CustomConverter.php index d11e027..c865bbc 100644 --- a/common/components/parsers/CustomConverter.php +++ b/common/components/parsers/CustomConverter.php @@ -4,6 +4,7 @@ namespace common\components\parsers; use common\components\CustomVarDamp; use yii\multiparser\Converter; use backend\models\Details; +use backend\models\DetailsCrosses; use backend\models\ImportersPrefix; class CustomConverter extends Converter @@ -93,6 +94,22 @@ class CustomConverter extends Converter return $row; } + public static function convertToCrosses( array $row ) + { + + $details_model = new DetailsCrosses(); + // проверим все ли обязательные колонки были указаны пользователем + $details_model->load(['DetailsCrosses' => $row]); + + if (!$details_model->validate()) { + $errors = ''; + foreach ($details_model->errors as $key => $arr_errors) { + $errors .= "Аттрибут $key - " . implode(' , ', $arr_errors); + } + throw new \ErrorException($errors); + } + return $row; + } public function ConvertToMultiply(array $row) { $PRICE = $row['PRICE']; @@ -125,31 +142,47 @@ class CustomConverter extends Converter } - public static function convertToArticle( array $row ) + public static function convertToArticle( $value, $importer_id = '' ) { - if ( isset($row['ARTICLE']) ) { + if(isset( $importer_id )){ + self::$importer_id = $importer_id; + } + + if (is_array($value)) { + $row = $value; // 1. Уберем префикс который разделен пробелом (если он есть) $words = explode(" ", $row['ARTICLE']); if (count($words) > 1) { array_shift($words); $row['ARTICLE'] = implode(" ", $words); } - } - if( isset( $row['BRAND'] ) && isset( self::$importer_id ) ){ - // 2. Уберем брендовый префикс (если он есть) - $prefix = ''; - // запрос закешируем - $prefix = ImportersPrefix::getDb()->cache( function ($db, $configuration, $row ) { - return ImportersPrefix::find()->where([ 'importer_id' => self::$importer_id, - 'brand' => $row['BRAND'] ])->one(); + + if( isset( $row['BRAND'] ) && isset( self::$importer_id ) ){ + // 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']); + if ($prefix) { + $row['BRAND'] = str_replace($prefix, "", $row['BRAND']); + } } + + return $row; + + } else { + $words = explode( " ", $value ); + if ( count( $words ) > 1) { + array_shift( $words ); + $value = implode( " ", $words ); + } + + return $value; } - return $row; } public static function convertToBrand($value) diff --git a/common/components/parsers/config.php b/common/components/parsers/config.php index cb61193..66ffb58 100644 --- a/common/components/parsers/config.php +++ b/common/components/parsers/config.php @@ -17,12 +17,11 @@ 'hasKey' => 1, 'configuration' => ["string" => 'DESCR', "float" => 'PRICE', - "brand" => 'BRAND[', + "brand" => 'BRAND', "integer" => ['BOX','ADD_BOX'], "multiply" => [], - "details" => [], - "article" => [] - + "article" => [], + "details" => [] ] ],], @@ -36,6 +35,20 @@ "ADD_BOX"=> 'В пути', "GROUP" => 'Группа RG' ], + + 'crosses' => ['class' => 'common\components\parsers\CustomCsvParser', + 'auto_detect_first_line' => true, + 'min_column_quantity' => 4, + 'hasHeaderRow' => true, + 'keys' =>['ARTICLE', 'CROSS_ARTICLE', 'BRAND', 'CROSS_BRAND'], + 'converter_conf' => [ + //'class' => ' common\components\parsers\CustomConverter', + 'hasKey' => 1, + 'configuration' => [ + "brand" => ['BRAND', 'CROSS_BRAND'], + "crosses" => [], + ] + ],], ], 'xml' => ['console' => -- libgit2 0.21.4