Commit 7776ca75d091b76ffba313f3fad0db7145276fc8

Authored by Mihail
1 parent 53edab28

add form, model and controller for uploaded crosses files (details_crosses

backend/controllers/CrossingUploadController.php 0 → 100644
  1 +<?php
  2 +/**
  3 + * Created by PhpStorm.
  4 + * User: Tsurkanov
  5 + * Date: 15.10.2015
  6 + * Time: 12:27
  7 + */
  8 +
  9 +namespace backend\controllers;
  10 +
  11 +use backend\components\base\BaseController;
  12 +use common\components\CustomVarDamp;
  13 +use yii\filters\AccessControl;
  14 +use backend\models\UploadFileCrossingForm;
  15 +use backend\models\DetailsCrosses;
  16 +use yii\web\UploadedFile;
  17 +use \Yii;
  18 +
  19 +class CrossingUploadController extends BaseController
  20 +{
  21 + public $layout = "/column";
  22 +
  23 + /**
  24 + * @inheritdoc
  25 + */
  26 + public function behaviors()
  27 + {
  28 + return [
  29 + 'access' => [
  30 + 'class' => AccessControl::className(),
  31 + 'rules' => [
  32 + [
  33 + 'actions' => ['index', 'result'],
  34 + 'allow' => true,
  35 + 'roles' => ['@'],
  36 + ],
  37 + ],
  38 + ],
  39 +// 'verbs' => [
  40 +// 'class' => VerbFilter::className(),
  41 +// 'actions' => [
  42 +// 'logout' => ['post'],
  43 +// ],
  44 +// ],
  45 + ];
  46 + }
  47 +
  48 + /**
  49 + * @inheritdoc
  50 + */
  51 + public function actions()
  52 + {
  53 + return [
  54 + 'error' => [
  55 + 'class' => 'yii\web\ErrorAction',
  56 + ],
  57 + ];
  58 + }
  59 +
  60 +
  61 + public function actionIndex()
  62 + {
  63 + $model = new UploadFileCrossingForm();
  64 + return $this->render('index', ['model' => $model]);
  65 + }
  66 +
  67 + public function actionResult()
  68 + {
  69 + $model = new UploadFileCrossingForm();
  70 + $data = [];
  71 + if ($model->load(Yii::$app->request->post())) {
  72 + $model->file = UploadedFile::getInstance($model, 'file');
  73 +
  74 + if ($model->validate()) {
  75 + $file_name = $model->file->name;
  76 + $model->file_path = Yii::getAlias('@temp_upload') . '/' . $file_name;
  77 +
  78 + $model->file->saveAs($model->file_path);
  79 + //запускаем парсинг
  80 + // доп. опции для парсера - удаление префикса в артикулах
  81 + $options['mode'] = 'crosses';
  82 + $fields = [];
  83 + if ($model->delete_prefix1) {
  84 + $fields[] = 'ARTICLE';
  85 + }
  86 + if ($model->delete_prefix2) {
  87 + $fields[] = 'CROSS_ARTICLE';
  88 + }
  89 + if ( $fields ) {
  90 + $options [ 'converter_conf' ] = [ 'configuration' => [ "article" => $fields ,
  91 + "string" => ['ARTICLE', 'CROSS_ARTICLE'],] ];
  92 + } else {
  93 + $options [ 'converter_conf' ] = [ 'configuration' => [ "string" => ['ARTICLE', 'CROSS_ARTICLE'], ] ];
  94 + }
  95 +
  96 + $data = $model->readFile( $options );
  97 + $crosses_model = new DetailsCrosses();
  98 + $crosses_model->ManualInsertWithIgnore( $data );
  99 +
  100 + Yii::$app->session->setFlash('success', 'Файл кроссов успешно загружен');
  101 + return $this->render('index', ['model' => $model]);
  102 + }else{
  103 + // не прошла валидация форма загрузки файлов
  104 + $errors_str = '';
  105 + foreach ($model->getErrors() as $error) {
  106 + $errors_str .= implode( array_values($error) );
  107 + }
  108 + throw new \ErrorException( $errors_str );
  109 + }
  110 +
  111 + } else {
  112 + throw new \ErrorException( 'Ошибка загрузки данных' );
  113 + }
  114 + }
  115 +}
0 116 \ No newline at end of file
... ...
backend/controllers/ParserController.php
... ... @@ -10,7 +10,6 @@ use backend\models\UploadFileParsingForm;
10 10 use yii\web\UploadedFile;
11 11 use yii\data\ArrayDataProvider;
12 12 use yii\multiparser\DynamicFormHelper;
13   -use backend\components\parsers\CustomParserConfigurator;
14 13 use backend\models\ImportersFiles;
15 14 use backend\models\Importers;
16 15 use yii\base\ErrorException;
... ...
backend/models/DetailsCrosses.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  
7 8 /**
... ... @@ -17,6 +18,10 @@ use Yii;
17 18 class DetailsCrosses extends \backend\components\base\BaseActiveRecord
18 19 {
19 20 /**
  21 + * int - размер пакета запроса
  22 + */
  23 + const BATCH = 1000;
  24 + /**
20 25 * @inheritdoc
21 26 */
22 27 public static function tableName()
... ... @@ -50,4 +55,31 @@ class DetailsCrosses extends \backend\components\base\BaseActiveRecord
50 55 'timestamp' => Yii::t('app', 'Timestamp'),
51 56 ];
52 57 }
  58 +
  59 + /**
  60 + * вставка данных с игнором дублей прямым запросом SQL
  61 + * @param $data - массив вставляемых данный, вставка будет прозводится пакетами размером указанным в константе BATCH
  62 + * @throws \yii\db\Exception
  63 + */
  64 + //@todo - вынести все ручные инсерты в отдельный класс
  65 + public function ManualInsertWithIgnore( $data )
  66 + {
  67 + // \common\components\CustomVarDamp::dumpAndDie($data);
  68 + $table_name = self::tableName();
  69 + $keys_arr = array_keys($data[0]);
  70 +
  71 + // запросы будем выполнять пакетами
  72 + // размер пакета установлен в константе
  73 + // разобъем массив на пакеты и будем их проходить
  74 + $data = array_chunk($data, $this::BATCH);
  75 + foreach ($data as $current_batch_array) {
  76 +
  77 + //воспользуемся пакетной вставкой от фреймворка
  78 + $query = Yii::$app->db->createCommand()->batchInsert($table_name, $keys_arr, $current_batch_array)->sql;
  79 + // добавим ключевое слово - ignore
  80 + $query = preg_replace('/INSERT/','INSERT IGNORE', $query);
  81 + Yii::$app->db->createCommand($query)->execute();
  82 +
  83 + }
  84 + }
53 85 }
... ...
backend/models/UploadFileCrossingForm.php 0 → 100644
  1 +<?php
  2 +namespace backend\models;
  3 +
  4 +use yii\base\ErrorException;
  5 +use yii\base\Model;
  6 +use yii\web\UploadedFile;
  7 +use Yii;
  8 +use common\components\CustomVarDamp;
  9 +
  10 +/**
  11 + * UploadForm is the model behind the upload form.
  12 + */
  13 +class UploadFileCrossingForm extends Model
  14 +{
  15 + /**
  16 + * @var UploadedFile file attribute
  17 + */
  18 + // атрибуты формы
  19 + public $file;
  20 + public $delete_prefix1;
  21 + public $delete_prefix2;
  22 + public $file_path;
  23 +
  24 +
  25 +
  26 + public function rules()
  27 + {
  28 + return [
  29 + ['file', 'required', 'message' => 'Не выбран файл!' ],
  30 + [['file'], 'file', 'extensions' => 'csv', 'checkExtensionByMimeType'=>false ],
  31 + [['delete_prefix1', 'delete_prefix2'], 'boolean' ],
  32 +
  33 + ];
  34 + }
  35 +
  36 + public function attributeLabels()
  37 + {
  38 + return [
  39 + 'file' => Yii::t('app', 'Источник'),
  40 + 'delete_prefix1' => Yii::t('app', 'Удалять префикс в артикуле товара 1'),
  41 + 'delete_prefix2' => Yii::t('app', 'Удалять префикс в артикуле товара 2'),
  42 + ];
  43 + }
  44 +
  45 + public function readFile( $options = [] ){
  46 +
  47 + $data = Yii::$app->multiparser->parse( $this->file_path, $options );
  48 + if( !is_array( $data ) ){
  49 + throw new ErrorException("Ошибка чтения из файла кроссов {$this->file_path}");
  50 + }
  51 + // файл больше не нужен - данные прочитаны и сохранены в кеш
  52 + if( file_exists($this->file_path) )
  53 + unlink($this->file_path);
  54 +
  55 + return $data;
  56 + }
  57 +
  58 + public function fields()
  59 + {
  60 + return [
  61 +
  62 + 'delete_price1',
  63 + 'delete_price2'
  64 +
  65 + ];
  66 + }
  67 +
  68 +
  69 +}
0 70 \ No newline at end of file
... ...
backend/models/UploadFileParsingForm.php
... ... @@ -48,9 +48,7 @@ class UploadFileParsingForm extends Model
48 48 return [
49 49 ['importer_id', 'required', 'message' => 'Не указан поставщик!' ],
50 50 ['file', 'required', 'message' => 'Не выбран файл!' ],
51   - //@todo - not working this file validator!!! - fixed
52   - [['file'], 'file'],// 'extensions' => ['csv', 'xml'] ],
53   - // 'wrongMimeType' => 'Указан неподдерживаемый тип файла. Можно выбирать csv, xml файлы.' ],
  51 + [['file'], 'file', 'extensions' => ['csv', 'xml'], 'checkExtensionByMimeType'=>false ],
54 52 ['importer_id', 'integer','max' => 999999, 'min' => 0 ],
55 53 [['action','delete_prefix', 'delete_price', 'success'], 'boolean', 'except' => 'auto' ], // только для ручной загрузки
56 54 ['delimiter', 'string', 'max' => 1],
... ...
backend/views/crossing-upload/index.php 0 → 100644
  1 +<?php
  2 +use yii\widgets\ActiveForm;
  3 +use yii\helpers\Html;
  4 +use yii\helpers\ArrayHelper;
  5 +
  6 +?>
  7 +<div class="row">
  8 + <div class="col-lg-5">
  9 + <?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data',],'action'=>['crossing-upload/result']]);
  10 +
  11 + if ($msg = \Yii::$app->session->getFlash('success')) { // вернулись после успешной загрузки данного файла
  12 + echo Html::tag('h3', $msg ,['class'=>'bg-success']);
  13 + }
  14 + ?>
  15 + <h3>Кросс файлы</h3>
  16 +
  17 + <?= $form->field($model, 'delete_prefix1')->checkbox() ?>
  18 + <?= $form->field($model, 'delete_prefix2')->checkbox() ?>
  19 + <?= $form->field($model, 'file')->fileInput()->label(false) ?>
  20 +
  21 + <div class="form-group">
  22 + <?= Html::submitButton(Yii::t( 'app', 'Выполнить' ), ['class' => 'btn btn-primary']) ?>
  23 + </div>
  24 +
  25 + <?php ActiveForm::end() ?>
  26 + </div>
  27 +</div>
  28 +
... ...
backend/views/layouts/column.php
... ... @@ -283,6 +283,7 @@ $this-&gt;beginContent(&#39;@app/views/layouts/main.php&#39;);
283 283 'options' => ['class' => 'sidebar-menu'],
284 284 'items' => [
285 285 ['label' => "Загрузка файлов", 'url' => ['#'], 'items' => [
  286 + ['label' => 'Кросс-файлы', 'url' => ['crossing-upload/index']],
286 287 ['label' => 'Файлы на сервере', 'url' => ['parser/server-files']],
287 288 ['label' => 'Загрузить файл на сервер', 'url' => ['parser/index', 'mode' => 1]],
288 289 ['label' => 'Ручная загрузка', 'url' => ['parser/index']],
... ...
common/components/PriceWriter.php
... ... @@ -65,8 +65,15 @@ class PriceWriter
65 65 $row['PRICE'] = \Yii::$app->multiparser->convertToFloat($row['PRICE']);
66 66 $row['BOX'] = \Yii::$app->multiparser->convertToInteger($row['BOX']);
67 67 // присвоим полный артикул
  68 +
68 69 $row['FULL_ARTICLE'] = $row['ARTICLE'];
69   - $row['ARTICLE'] = \Yii::$app->multiparser->convertToArticle( $row );
  70 + if ((int)$this->configuration['delete_prefix']) {
  71 + $row = \Yii::$app->multiparser->convertToArticle( $row, $this->configuration['importer_id'] );
  72 + } else {
  73 + $row['ARTICLE'] = \Yii::$app->multiparser->convertToArticle( $row['ARTICLE'] );
  74 + }
  75 +
  76 +
70 77 if (isset($row['ADD_BOX']))
71 78 $row['ADD_BOX'] = \Yii::$app->multiparser->convertToInteger($row['ADD_BOX']);
72 79  
... ... @@ -84,9 +91,6 @@ class PriceWriter
84 91 try {
85 92 //@todo add transaction
86 93  
87   - if ((int)$this->configuration['delete_prefix']) {
88   - $details_model->delete_prefix = true;
89   - }
90 94 if ((int)$this->configuration['delete_price']) {
91 95 $details_model->delete_price = true;
92 96 }
... ...
common/components/parsers/CustomConverter.php
... ... @@ -4,6 +4,7 @@ namespace common\components\parsers;
4 4 use common\components\CustomVarDamp;
5 5 use yii\multiparser\Converter;
6 6 use backend\models\Details;
  7 +use backend\models\DetailsCrosses;
7 8 use backend\models\ImportersPrefix;
8 9  
9 10 class CustomConverter extends Converter
... ... @@ -93,6 +94,22 @@ class CustomConverter extends Converter
93 94 return $row;
94 95 }
95 96  
  97 + public static function convertToCrosses( array $row )
  98 + {
  99 +
  100 + $details_model = new DetailsCrosses();
  101 + // проверим все ли обязательные колонки были указаны пользователем
  102 + $details_model->load(['DetailsCrosses' => $row]);
  103 +
  104 + if (!$details_model->validate()) {
  105 + $errors = '';
  106 + foreach ($details_model->errors as $key => $arr_errors) {
  107 + $errors .= "Аттрибут $key - " . implode(' , ', $arr_errors);
  108 + }
  109 + throw new \ErrorException($errors);
  110 + }
  111 + return $row;
  112 + }
96 113 public function ConvertToMultiply(array $row)
97 114 {
98 115 $PRICE = $row['PRICE'];
... ... @@ -125,31 +142,47 @@ class CustomConverter extends Converter
125 142  
126 143 }
127 144  
128   - public static function convertToArticle( array $row )
  145 + public static function convertToArticle( $value, $importer_id = '' )
129 146 {
130   - if ( isset($row['ARTICLE']) ) {
  147 + if(isset( $importer_id )){
  148 + self::$importer_id = $importer_id;
  149 + }
  150 +
  151 + if (is_array($value)) {
131 152  
  153 + $row = $value;
132 154 // 1. Уберем префикс который разделен пробелом (если он есть)
133 155 $words = explode(" ", $row['ARTICLE']);
134 156 if (count($words) > 1) {
135 157 array_shift($words);
136 158 $row['ARTICLE'] = implode(" ", $words);
137 159 }
138   - }
139   - if( isset( $row['BRAND'] ) && isset( self::$importer_id ) ){
140   - // 2. Уберем брендовый префикс (если он есть)
141   - $prefix = '';
142   - // запрос закешируем
143   - $prefix = ImportersPrefix::getDb()->cache( function ($db, $configuration, $row ) {
144   - return ImportersPrefix::find()->where([ 'importer_id' => self::$importer_id,
145   - 'brand' => $row['BRAND'] ])->one();
  160 +
  161 + if( isset( $row['BRAND'] ) && isset( self::$importer_id ) ){
  162 + // 2. Уберем брендовый префикс (если он есть)
  163 + $prefix = '';
  164 + // запрос закешируем
  165 + $prefix = ImportersPrefix::getDb()->cache( function ($db, $configuration, $row ) {
  166 + return ImportersPrefix::find()->where([ 'importer_id' => self::$importer_id,
  167 + 'brand' => $row['BRAND'] ])->one();
146 168 });
147 169  
148   - if ($prefix) {
149   - $row['BRAND'] = str_replace($prefix, "", $row['BRAND']);
  170 + if ($prefix) {
  171 + $row['BRAND'] = str_replace($prefix, "", $row['BRAND']);
  172 + }
150 173 }
  174 +
  175 + return $row;
  176 +
  177 + } else {
  178 + $words = explode( " ", $value );
  179 + if ( count( $words ) > 1) {
  180 + array_shift( $words );
  181 + $value = implode( " ", $words );
  182 + }
  183 +
  184 + return $value;
151 185 }
152   - return $row;
153 186 }
154 187  
155 188 public static function convertToBrand($value)
... ...
common/components/parsers/config.php
... ... @@ -17,12 +17,11 @@
17 17 'hasKey' => 1,
18 18 'configuration' => ["string" => 'DESCR',
19 19 "float" => 'PRICE',
20   - "brand" => 'BRAND[',
  20 + "brand" => 'BRAND',
21 21 "integer" => ['BOX','ADD_BOX'],
22 22 "multiply" => [],
23   - "details" => [],
24   - "article" => []
25   -
  23 + "article" => [],
  24 + "details" => []
26 25 ]
27 26 ],],
28 27  
... ... @@ -36,6 +35,20 @@
36 35 "ADD_BOX"=> 'В пути',
37 36 "GROUP" => 'Группа RG'
38 37 ],
  38 +
  39 + 'crosses' => ['class' => 'common\components\parsers\CustomCsvParser',
  40 + 'auto_detect_first_line' => true,
  41 + 'min_column_quantity' => 4,
  42 + 'hasHeaderRow' => true,
  43 + 'keys' =>['ARTICLE', 'CROSS_ARTICLE', 'BRAND', 'CROSS_BRAND'],
  44 + 'converter_conf' => [
  45 + //'class' => ' common\components\parsers\CustomConverter',
  46 + 'hasKey' => 1,
  47 + 'configuration' => [
  48 + "brand" => ['BRAND', 'CROSS_BRAND'],
  49 + "crosses" => [],
  50 + ]
  51 + ],],
39 52 ],
40 53 'xml' =>
41 54 ['console' =>
... ...