3 namespace app\controllers;
10 use app\models\Locations;
11 use app\models\ItemsSearch;
12 use app\models\MovingSearch;
13 use app\models\Status;
15 use yii\web\Controller;
16 use yii\web\NotFoundHttpException;
17 use yii\web\UploadedFile;
18 use yii\filters\VerbFilter;
23 #require "/vendor/phpoffice/phpexcel/Classes/PHPExcel.php";
26 * ItemsController implements the CRUD actions for Items model.
28 class ItemsController extends Controller
33 public function behaviors()
37 'class' => VerbFilter::className(),
39 'delete' => [ 'POST' ],
46 * Добавление предмета/оборудование если его нет
47 * @param array $options
52 * string|NULL 'netname'
55 * string|NULL 'serial'
56 * string|NULL 'product'
57 * string|NULL 'modelnumber'
58 * @return integer|FALSE
60 public static function addIfNeed($options)
64 'error' => Yii::t('items', 'Items: Key field missing "invent", "serial", "model"') . print_r($options, TRUE),
66 // Если указан инвентарный номер
67 if (is_array($options) && isset($options[ 'invent' ]))
70 ->where([ 'invent' => $options[ 'invent' ]]); // Ищем оборудование с инвентарным номером.
71 // Если указан серийный номер
72 if (isset($options[ 'serial' ])) {
73 $item = $item->andWhere([ 'like', 'serial', $options[ 'serial' ]]); // Ищем дополнительно с серийным номером
75 $item = $item->all(); // Получаем все записи
77 if (count($item) > 0) // Записи найдены, выводим первую совпавшую
79 $result[ 'id' ] = $item[ 0 ]->id;
80 $result[ 'error' ] = '';
84 // Внесённого оборудования не найдено. Добавим новую запись
85 if (isset($options[ 'model' ]))
87 $model = ModelsController::addIfNeed($options);
88 if ($model[ 'id' ] === FALSE)
90 $result[ 'error' ] .= $model[ 'error' ] . '<br />';
92 // Создаём новую запись предмета/оборудования
94 $item->name = isset($options[ 'netName' ]) ? $options[ 'netName' ] : NULL; // Сетевое имя
96 $item->model_id = $model[ 'id' ]; // идентификатор модели (Подготовлено для преобразования)
98 $item->invent = isset($options[ 'invent' ]) ? $options[ 'invent' ] : NULL; // Инвентарный номер
99 $item->comment = isset($options[ 'comment' ]) ? $options[ 'comment' ] : NULL; // Коментарий
100 $item->os = isset($options[ 'os' ]) ? $options[ 'os' ] : NULL; // Операционная система
101 $item->mac = isset($options[ 'mac' ]) ? $options[ 'mac' ] : NULL; // MAC-адрес
102 $item->serial = isset($options[ 'serial' ]) ? $options[ 'serial' ] : NULL; // Серийный номер
103 $item->checked = false; // Не инвентризирован (требует внимания после импорта)
105 if ($item->validate() && $item->save())
107 $result[ 'id' ] = $item->id; // Возвращаем идентификатор записанного оборудования
108 $result[ 'error' ] = '';
112 $result[ 'error' ] .= Yii::t('items', 'Items: Failed to add entry') . print_r($item->errors(), TRUE);
121 * Формирование PDF файла для печати QR-кодов для наклеек
122 * @param integer|array|null id
125 public function actionPrint()
127 if (! User::canPermission('takingInventory') ) {
128 return $this->redirect(['site/index']);
130 // Список предметов/оборудования, если есть
131 $id = Yii::$app->request->get('id');
133 $models = Items::find();
137 $models = $models->where([ 'in', 'id', $id ]); // Несколько предметов/оборудования
140 $models = $models->where([ 'id' => $id ]); // Один предмет/оборудование
142 $models = $models->all(); // Формирование списка
144 $pdf = Yii::$app->pdf; // Pабота с PDF
146 $pdf->methods[ 'SetHeader' ] = ''; // Yii::t('items', 'Items');
147 $pdf->methods[ 'SetFooter' ] = ''; // ['{PAGENO}'];
149 $pdf->marginLeft = 5;
150 $pdf->marginRight = 5;
152 $pdf->marginBottom = 15;
153 // Имя файла для выгрузки, по умолчанию document.pdf
154 $pdf->filename = Yii::t('app', Yii::$app->name) . ' (' . Yii::t('items', 'Items') . ').pdf';
156 // Заполнение страницы данными
157 $pdf->content = $this->renderPartial('print', [ 'models' => $models ]);
160 return $pdf->render();
164 * Процедура начала инвентаризации.
167 public function actionStart_checking()
169 // Проверка доступа для проведения инвентаризации
170 if (! User::canPermission('takingInventory') ) {
171 // Переход к списку предметов/оборудования, если доступ не разрешён.
172 return $this->redirect(['index']);
174 // Запрос на получение списка идентификаторов предметов/оборудования, которые списаны
175 $modelS = Moving::find()
178 ->Where([ 'ilike', Status::tableName() . '.name', 'Списано' ]);
180 // Получаем список всех предметов/оборудования, кроме списанного
181 $model = Items::find()
183 ->innerJoin([ 'm' => $modelS ], 'not m.item_id = id')
186 // Устанавливаем флаг непроинвентаризированных для всех предметов/оборудования из полученного списка.
187 Items::updateAll([ 'checked' => false ], [ 'in', 'id', $model ]);
189 // Переход к списку предметов/оборудования.
190 return $this->redirect([ 'index' ]);
195 * @param string|null $qrcheck считанный QR-код
198 public function actionCheck()
200 // Проверка доступа для проведения инвентаризации
201 if (! User::canPermission('takingInventory') ) {
202 // Показ стартовой страницы, если доступ не разрешён.
203 return $this->redirect(['site/index']);
206 $model = new Check();
208 if ($model->load(Yii::$app->request->post()))
210 if ((! empty($model->qrcheck)) && strpos($model->qrcheck, ',') !== false)
212 $keys = explode(',', $model->qrcheck);
213 Items::updateAll([ 'checked' => true ], [ 'invent' => trim($keys[ 0 ]), 'serial' => trim($keys[ 1 ]) ]);
214 $items = Items::find()->where([ 'invent' => trim($keys[ 0 ]), 'serial' => trim($keys[ 1 ]) ])->all();
215 //$message = '[0] = "' . $keys[0] . '", [1] = "' . $keys[1] . '"<br />';
216 foreach ($items as $row)
218 $message .= $row->modelName . ' (' . $row->id . ')';
221 $message = Yii::t('items', 'Checked item(s): ') . $message;
222 $model->qrcheck = '';
225 $searchModel = new ItemsSearch();
226 $dataProvider = $searchModel->noinvent($model);
228 return $this->render('check', [
229 'message' => $message,
231 'searchModel' => $searchModel,
232 'dataProvider' => $dataProvider,
237 * Список всех предметов/оборудования.
240 public function actionIndex()
242 if (! User::canPermission('createRecord') )
244 return $this->redirect(['site/index']);
246 $searchModel = new ItemsSearch();
247 if (isset(Yii::$app->request->queryParams['id']))
249 $id = Yii::$app->request->queryParams['id'];
250 $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
251 //$dataProvider->query->select(Items::tableName() . '.id');
252 $pageSize = $dataProvider->pagination->pageSize;
253 $dataProvider->pagination = FALSE;
254 $rows = $dataProvider->getModels();
256 foreach ($rows as $key => $val)
260 $page = ceil(($key + 1) / $pageSize);
264 return $this->redirect(['index', 'page' => $page]);
266 $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
268 return $this->render('index', [
269 'searchModel' => $searchModel,
270 'dataProvider' => $dataProvider,
275 * Импортирование строк товаров из массива
277 public function doImport($arrayRows)
279 // Инициализация счётчиков
281 'countRows' => count($arrayRows),
282 'countImported' => 0,
288 // Проверка наличия ключевых полей
289 if ((!isset($arrayRows[ 0 ][ 'model' ]))
290 || (!isset($arrayRows[ 0 ][ 'invent' ]))
291 || (!isset($arrayRows[ 0 ][ 'location' ]))
292 || (!isset($arrayRows[ 0 ][ 'region' ]))
293 || (!isset($arrayRows[ 0 ][ 'date' ]))
296 // Сообщение об ошибке
297 $arrayReturn[ 'countErrors' ] = count($arrayRows);
298 $arrayReturn[ 'errors' ] .= '<br />' . Yii::t('import', 'Skip all. Key column not found: ') . print_r($arrayRows[0], TRUE);
302 // Просмотрим весь массив
303 foreach($arrayRows as $row)
305 // ПОлучим местоположения
306 $location = LocationsController::addIfNeed($row); // Получение идентификатора расположения
307 if ( $location[ 'id' ] === FALSE)
310 $arrayReturn[ 'countErrors' ]++;
311 $arrayReturn[ 'errors' ] .= '<br />' . Yii::t('import', 'Location: {location} ({region})', $row) . ' :: ' . $location[ 'error' ];
315 // Попробуем найти или добавить предмет/оборудование
316 $item = $this->addIfNeed($row);
317 if ($item[ 'id' ] === FALSE)
319 $arrayReturn[ 'countErrors' ]++;
320 $arrayReturn[ 'errors' ] .= '<br />' . $item[ 'error' ];
324 // Проверка, что предмет/оборудование уже были в базе
325 $item = Items::find()->where([ 'id' => $item[ 'id' ]])->one();
326 if ($item->checked === TRUE)
328 $arrayReturn[ 'countExists' ]++;
332 $state = isset($row[ 'status' ]) ? StatusController::addIfNeed($row) : StatusController::addIfNeed([ 'name' => 'Склад' ]);
333 if ( $state[ 'id' ] === FALSE )
336 $arrayReturn[ 'countErrors' ]++;
337 $arrayReturn[ 'errors' ] .= '<br />' . $state[ 'error' ];
341 // Новый предмет/оборудование. Пробуем добавить первое перемещение
342 $moving = new Moving();
343 $moving->date = $row[ 'date' ];
344 $moving->state_id = $state[ 'id' ];
345 $moving->item_id = $item[ 'id' ];
346 $moving->location_id = $location[ 'id' ];
347 $moving->comment = Yii::t('import', 'Import: {comment}', $row);
349 if ($moving->validate() && $moving->save())
351 // Записаали первое движение
352 $arrayReturn[ 'countImported' ]++;
356 // Запись не удалась, пробуем удалить предмет/оборудование
357 Items::find()->where([ 'id' => $item_id, 'checked' => FALSE ])->one()->delete();
359 $arrayReturn[ 'countErrors' ]++;
360 $arrayReturn[ 'errors' ] .= '<br />' . Yii::t('import', 'Moving: {date} (') . $moving->errors['date'][0]. Yii::t('import', '), Inventory number:{invent}, model: {model}, location: {location} ( {region} )' , $row);
370 // Возврат результата импорта
375 * Импорт данных из файла csv
376 * Структура файла данных при выгрузке из 1С: (Колонки могут меняться.
377 * | № п/п | | Предмет/оборудование | | | | | | | Инвентарный номер | Материально отвественное лицо | | | Место размещения | Регион/подразделение | Количество |
378 * Так как 1С из коробки не умеет выгружать форму в .csv, то приходится сначала выгрузить в .xls(x), и уже из MS Excel/Lible office Calc сохранять в .csv
380 public function actionImport()
382 if (! User::canPermission('updateRecord') ) {
383 return $this->redirect(['site/index']);
385 $model = new Import();
387 $searchModel = new ItemsSearch();
388 $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
389 if (Yii::$app->request->isPost)
394 'npp' => Yii::t('import', 'No. in order'),
395 'model' => Yii::t('import', 'Primary means'),
396 'netname' => Yii::t('import', 'Network name'),
397 'invent' => Yii::t('import', 'Inventory number'),
398 'comment' => Yii::t('import', 'Financially responsible person'),
399 'os' => Yii::t('import', 'Operation system'),
400 'mac' => Yii::t('import', 'MAC address'),
401 'serial' => Yii::t('import', 'Serial number'),
402 'product' => Yii::t('import', 'Product number'),
403 'modelnumber' => Yii::t('import', 'Model number'),
404 'date' => Yii::t('import', 'Date of acceptance for registration'),
405 'location' => Yii::t('import', 'Location'),
406 'region' => Yii::t('import', 'Region'),
407 'type' => Yii::t('import', 'Type'),
408 'status' => Yii::t('import', 'State'),
410 $model->filecsv = UploadedFile::getInstance($model, 'filecsv');
411 if ($model->upload())
413 $fileName = 'upload/' . $model->filecsv->baseName . '.' . $model->filecsv->extension;
414 $handle = fopen($fileName, 'r');
415 if ($handle !== FALSE)
417 if (strcasecmp($model->filecsv->extension, 'csv') === 0 )
419 // Построчное чтение CSV файла
420 while (($row = fgetcsv($handle, 2048, ';')) !== false )
422 // Пока не собраны индексы столбцов из шапки
423 if (count($columns) == 0)
425 // Ищем строку с заголовком таблицы
426 if ( stripos($row[0], $columnNames[ 'npp' ]) !== FALSE )
428 // Перебираем все колонки
429 foreach ($row as $key => $item)
431 // Перебираем все названия заголовков колонок
432 foreach($columnNamses as $name => $text)
434 // Если название совпало,
435 if (stripos($item, $text) !== FALSE)
437 // Сохраняем индек колонки
438 $columns[ $name ] = $key;
446 // Перебираем предметы/оборудование (Номер по порядку должен быть целым числом)
447 if (ctype_digit(str_replace(' ', '', $row[ $columns[ 'npp' ]])))
449 // Заполняем очередную строку для таблицы
451 foreach ($columns as $key => $index)
453 $line[ $key ] = $row[ $index ];
455 if (isset($line[ 'date' ]))
457 if ($line[ 'date' ] == '#NULL!') $line[ 'date' ] = date('d.m.Y');
461 $line[ 'date' ] = date('d.m.Y');
463 array_push($rows, $line);
466 } // Перебор строк файла
470 $inputFileType = \PHPExcel_IOFactory::identify($fileName); // Получение типа данных в файле
471 $excelReader = \PHPExcel_IOFactory::createReader($inputFileType); // Создание потока чтения из файла
472 $excelObj = $excelReader->load($fileName); // Открытие файла
473 $worksheet = $excelObj->getSheet(0); // Работаем только с первым листом (обычно туда выгружает 1С)
476 // Цикл по всем строкам
477 foreach ($worksheet->getRowIterator() as $row)
479 $cellIterator = $row->getCellIterator(); // Получаем итератор ячеек в строке
480 $cellIterator->setIterateOnlyExistingCells(FALSE); // Указываем проверять даже не установленные ячейки
482 if (count($columns) == 0) // Пока не найдена шапка, проверяем строку
485 foreach ($cellIterator as $key => $item)
487 if (($key == 'A') && (stripos($item->getValue(), $columnsNames[ 'npp' ]) !== FALSE)) $flag = TRUE;
490 foreach ($columnsNames as $name => $text)
492 if (stripos($item->getValue(), $text) !== FALSE)
494 $columns[ $name ] = $key;
504 foreach ($cellIterator as $key => $item)
506 if ($key == $columns[ 'npp' ])
508 $npp = str_replace(' ', '', $item->getValue());
509 if (ctype_digit($npp)) $flag = TRUE;
513 foreach($columns as $keym => $index)
515 if ($index == $key) $line[ $keym ] = $item->getValue();
521 if (isset($line[ 'date' ]))
523 if ($line[ 'date' ] == '#NULL!') $line[ 'date' ] = date('d.m.Y');
527 $line[ 'date' ] = date('d.m.Y');
529 array_push($rows, $line);
535 $res = $this->doImport($rows);
537 $message .= Yii::t('items', 'Read {countRows} records.<br />Imported {countImported} Items.<br />Exists {countExists} Items.<br />Error read {countErrors} records.<br />{errors}', $res);
540 return $this->render('import',[
541 'message' => $message,
543 'searchModel' => $searchModel,
544 'dataProvider' => $dataProvider,
549 * Показ одного предмета/оборудования. (не используется)
552 * @throws NotFoundHttpException если предмет/оборудование отсутствует
554 public function actionView($id)
556 if (! User::canPermission('updateRecord') ) {
557 return $this->redirect([ 'index', 'id' => $id ]);
559 return $this->render('view', [
560 'model' => $this->findModel($id),
565 * Создание нового предмета/оборудования.
568 public function actionCreate()
570 if (! User::canPermission('createRecord') ) {
571 return $this->redirect(['site/index']);
573 $model = new Items(); // Новый предмет/оборудование
574 $model->checked = true;
575 $modelm = new Moving();
576 if ($model->load(Yii::$app->request->post()) && $model->save())
578 // Удалось сохранить, создаём первую запись движения
579 if ($modelm->load(Yii::$app->request->post()))
581 $modelm->item_id = $model->id;
582 $modelm->comment = 'Поступление';
584 if ( $modelm->save() ) // Пробуем сохранить движение
586 return $this->redirect([ 'index', 'id' => $model->id ]); // Если удалось, показываем список оборудования
589 $this->findModel($model->id)->delete(); // Иначе удаляем созданную запись предмета/оборудования
590 unset($model->id); // Очищаем идентификатор предмета/оборудования
591 $model->isNewRecord = true;
592 return $this->render('create', [ // Показываем форму создания нового предмета/оборудования
599 $this->findModel($model->id)->delete(); // Иначе удаляем созданную запись предмета/оборудования
600 unset($model->id); // Очищаем идентификатор предмета/оборудования
601 $model->isNewRecord = true;
602 return $this->render('create', [ // Показываем форму создания нового предмета/оборудования
607 } else // не удалось сохранить - отображаем форму создания нового предмета/оборудования
609 return $this->render('create', [
618 * Изменение существующего предмета/оборудвания.
619 * Если премет/обрудование сохранён, то возвращаемся на страницу списка всех предметов/оборудования.
622 * @throws NotFoundHttpException если предмет/оборудование отсутствует
624 public function actionUpdate($id)
626 if (! User::canPermission('updateRecord') ) {
627 return $this->redirect(['index']);
629 $model = $this->findModel($id);
631 if ($model->load(Yii::$app->request->post()) && $model->save())
633 return $this->redirect([ 'index', 'id' => $model->id ]);
636 $searchModelM = new MovingSearch([ 'item_id' => $model->id ]);
637 $dataProviderM = $searchModelM->search(Yii::$app->request->queryParams);
639 return $this->render('update', [
640 'searchModelM' => $searchModelM,
641 'dataProviderM' => $dataProviderM,
647 * Удаляет сушествующий предмет/оборудование.
648 * Если премет/обрудование удалён, то возвращаемся на страницу списка всех предметов/оборудования.
651 * @throws NotFoundHttpException if the model cannot be found
653 public function actionDelete($id)
655 if (! User::canPermission('updateRecord') ) {
656 return $this->redirect(['site/index']);
658 $this->findModel($id)->delete();
660 return $this->redirect([ 'index' ]);
664 * Finds the Items model based on its primary key value.
665 * If the model is not found, a 404 HTTP exception will be thrown.
667 * @return Items the loaded model
668 * @throws NotFoundHttpException если предмет/оборудование отсутствует
670 protected function findModel($id)
672 if (($model = Items::findOne($id)) !== null)
677 throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.'));