Исправление контроллера ошибок и моделей продуктов

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-04-11 04:03:32 +10:00
parent 912b25bea2
commit c169347687
4 changed files with 411 additions and 357 deletions

View File

@ -15,16 +15,16 @@ class ErrorController extends Controller
// Исключение не выброшено // Исключение не выброшено
// Запись кода ошибки // Запись кода ошибки
$statusCode = $exception->statusCode; $statusCode = $exception->statusCode ?? $exception->getCode() ?? 0;
// Запись названия ошибки // Запись названия ошибки
$name = match ($exception->statusCode) { $name = match ($statusCode) {
404 => '404 (Не найдено)', 404 => '404 (Не найдено)',
default => $exception->getName() default => $exception->getName()
}; };
// Запись сообщения об ошибке // Запись сообщения об ошибке
$message = match ($exception->statusCode) { $message = match ($statusCode) {
404 => 'Страница не найдена', 404 => 'Страница не найдена',
default => $exception->getMessage() default => $exception->getMessage()
}; };

View File

@ -2,415 +2,469 @@
declare(strict_types=1); declare(strict_types=1);
namespace app\controllers; namespace app\models;
use yii; use yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\Response;
use yii\web\HttpException;
use yii\web\UploadedFile; use yii\web\UploadedFile;
use yii\imagine\Image;
use app\models\Product; use app\models\traits\SearchByEdge;
use Exception;
use moonland\phpexcel\Excel;
class ProductController extends Controller /**
* Продукт (в ассортименте магазина)
*
* Представляет собой лот состоящий из предложений от поставщиков
*
* @see Supply Поставки для продуктов
*/
class Product extends Document
{ {
public function actionIndex(string $catn): array|string|null use SearchByEdge;
/**
* Сценарий импорта .excel документа
*
* Использовать для обхода правил при загрузке файла
*/
const SCENARIO_IMPORT_EXCEL = 'import_excel';
/**
* Сценарий импорта изображений
*
* Использовать для обхода правил при загрузке файла
*/
const SCENARIO_IMPORT_IMAGE = 'import_image';
/**
* Сценарий записи товара
*/
const SCENARIO_WRITE = 'write';
/**
* Файл .excel для импорта товаров
*/
public Excel|string|array|null $file_excel = null;
/**
* Изображение для импорта
*/
public UploadedFile|string|array|null $file_image = null;
/**
* Группа в которой состоит товар
*/
public ProductGroup|null $group = null;
/**
* Имя коллекции
*/
public static function collectionName(): string
{ {
if ($model = Product::searchByCatn($catn)) { return 'product';
// Товар найден
if (yii::$app->request->isAjax) {
// AJAX-POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return [
'main' => $this->renderPartial('index', compact('model')),
'redirect' => '/product/' . $catn,
'_csrf' => yii::$app->request->getCsrfToken()
];
}
return $this->render('index', compact('model'));
} else {
throw new HttpException(404);
}
}
public function actionEditTitle(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
goto end;
}
if ($model = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация
$text = yii::$app->request->get('text') ?? yii::$app->request->post('text') ?? 'Без названия';
$model->name = $text;
if ($model->save()) {
// Товар обновлён
$return['name'] = $text;
}
} }
/** /**
* Конец алгоритма * Свойства
*/ */
end: public function attributes(): array
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionEditCatn(string $catn): array|string|null
{ {
// Инициализация return array_merge(
$return = [ parent::attributes(),
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
goto end;
}
if ($model = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация
$text = yii::$app->request->get('text') ?? yii::$app->request->post('text') ?? 'Без названия';
$model->catn = $text;
if ($model->save()) {
// Товар обновлён
$return['main'] = $this->renderPartial('index', compact('model'));
}
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionEditDscr(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация
$text = yii::$app->request->get('text') ?? yii::$app->request->post('text') ?? 'Без названия';
$product->dscr = $text;
if ($product->save()) {
// Товар обновлён
$return['description'] = $text;
}
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionEditDmns(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация
$text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? '0';
$text or $text = '0';
$dimension = yii::$app->request->post('dimension') ?? yii::$app->request->get('dimension') ?? 'x';
$product->dmns = array_merge(
$product->dmns ?? [],
[ [
$dimension => $text 'catn',
'name',
// В библеотеке есть баг на название DESC (неизвестно в моей или нет)
'dscr',
'prod',
'dmns',
'imgs',
'time'
] ]
); );
if ($product->save()) {
// Товар обновлён
$return['dimension'] = $text;
}
} }
/** /**
* Конец алгоритма * Метки свойств
*/ */
end: public function attributeLabels(): array
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionWriteImage(string $catn): array|string|null
{ {
// Инициализация return array_merge(
$return = [ parent::attributeLabels(),
'_csrf' => yii::$app->request->getCsrfToken() [
]; 'catn' => 'Каталожный номер (catn)',
'name' => 'Название (name)',
if (is_null($catn)) { 'dscr' => 'Описание (dscr)',
// Не получен артикул 'prod' => 'Производитель (prod)',
'dmns' => 'Габариты (dmns)',
yii::$app->response->statusCode = 500; 'imgs' => 'Изображения (imgs)',
'time' => 'Срок доставки (time)',
goto end; 'file_excel' => 'Документ (file_excel)',
} 'file_image' => 'Изображение (file_image)',
'group' => 'Группа (group)'
if ($product = Product::searchByCatn($catn)) { ]
// Товар найден );
// Инициализация
$product->file_image = UploadedFile::getInstancesByName('images');
$product->scenario = $product::SCENARIO_IMPORT_IMAGE;
if ($product->importImages() > 0) {
// Товар обновлён
$return['main'] = $this->renderPartial('index', ['model' => $product]);
}
} }
/** /**
* Конец алгоритма * Правила
*/ */
end: public function rules(): array
{
if (yii::$app->request->isPost) { return array_merge(
// POST-запрос parent::rules(),
[
yii::$app->response->format = Response::FORMAT_JSON; [
'catn',
return $return; 'required',
'message' => 'Заполните поля: {attribute}',
'on' => self::SCENARIO_WRITE,
'except' => [self::SCENARIO_IMPORT_EXCEL, self::SCENARIO_IMPORT_IMAGE]
],
[
'catn',
'string',
'message' => '{attribute} должен быть строкой'
],
[
'imgs',
'arrayValidator',
'message' => '{attribute} должен быть массивом'
],
[
'dmns',
'arrayWithNumbersValidator',
'message' => '{attribute} должен быть массивом и хранить циферные значения'
],
[
'file_excel',
'required',
'message' => 'Заполните поля: {attribute}',
'on' => self::SCENARIO_IMPORT_EXCEL
],
[
'file_excel',
'file',
'skipOnEmpty' => false,
'extensions' => 'xlsx',
'checkExtensionByMimeType' => false,
'maxFiles' => 5,
'maxSize' => 1024 * 1024 * 30,
'wrongExtension' => 'Разрешены только документы в формате: ".xlsx"',
'message' => 'Проблема при чтении документа',
'on' => self::SCENARIO_IMPORT_EXCEL
],
[
'file_image',
'required',
'message' => 'Загрузите изображение',
'on' => self::SCENARIO_IMPORT_IMAGE
],
[
'file_image',
'file',
'skipOnEmpty' => false,
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
'checkExtensionByMimeType' => true,
'maxFiles' => 10,
'maxSize' => 1024 * 1024 * 30,
'wrongExtension' => 'Разрешены только изображения в формате: ".jpg", ".jpeg", ".png", ".gif", ".webp"',
'message' => 'Проблема при загрузке изображения',
'on' => self::SCENARIO_IMPORT_IMAGE
]
]
);
} }
if ($model = Product::searchByCatn($catn)) { /**
return $this->render('index', compact('model')); * Запись пустого продукта
} else { */
return $this->redirect('/'); public static function writeEmpty(string $catn): ?self
}
}
public function actionDeleteImage(string $catn): array|string|null
{ {
// Инициализация // Инициализация
$return = [ $model = new self;
'_csrf' => yii::$app->request->getCsrfToken()
];
$index = yii::$app->request->post('index') ?? yii::$app->request->get('index');
if (is_null($catn) || is_null($index)) { // Настройки
// Не получены обязательные параметры $model->catn = $catn;
yii::$app->response->statusCode = 500;
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация (буфер нужен из-за кривых сеттеров)
$buffer = $product->imgs;
// Удаление
unset($buffer[$index]);
// Запись // Запись
$product->imgs = $buffer; return $model->save() ? $model : null;
if ($product->save()) {
// Товар обновлён
$return['main'] = $this->renderPartial('index', ['model' => $product]);
}
} }
/** /**
* Конец алгоритма * Импорт изображений
*
* @return int Количество сохранённых изображений
*/ */
end: public function importImages(): int
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionWriteCover(string $catn): array|string|null
{ {
// Инициализация // Инициализация
$return = [ $amount = 0;
'_csrf' => yii::$app->request->getCsrfToken()
];
$index = yii::$app->request->post('index') ?? yii::$app->request->get('index');
if (is_null($catn) || is_null($index)) { if ($this->validate()) {
// Не получены обязательные параметры // Проверка пройдена
yii::$app->response->statusCode = 500; foreach ($this->file_image as $file) {
// Перебор обрабатываемых изображений
goto end; if (!file_exists(YII_PATH_PUBLIC . $catalog = '/img/products/' . $this->_key)) {
// Директория для изображений продукта не найдена
if (!mkdir(YII_PATH_PUBLIC . $catalog, 0775, true)) {
// Не удалось записать директорию
return false;
};
} }
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация (буфер нужен из-за кривых сеттеров) if (!file_exists(YII_PATH_PUBLIC . $catalog_h150 = '/img/products/' . $this->_key . '/h150')) {
$buffer = $product->imgs; // Директория для обложек изображений продукта не найдена
foreach($buffer as $image_index => &$image) { if (!mkdir(YII_PATH_PUBLIC . $catalog_h150, 0775, true)) {
// Перебор изображений // Не удалось записать директорию
if ($image_index === (int) $index) { return false;
// Найдено запрашиваемое изображение };
}
// Установка обложки // Запись на сервер
$image['covr'] = true; $file->saveAs(YII_PATH_PUBLIC . $catalog . '/' . $file->baseName . '.' . $file->extension . '.original');
} else {
$image['covr'] = false; // Конвертация изображения для сохранения полного изображения
Image::resize(YII_PATH_PUBLIC . $catalog . '/' . $file->baseName . '.' . $file->extension . '.original', 800, 800)
->save(YII_PATH_PUBLIC . $catalog . '/' . $file->baseName . '.' . $file->extension, ['quality' => 80]);
// Конвертация изображения для сохранения обложки (150px)
Image::resize(YII_PATH_PUBLIC . $catalog . '/' . $file->baseName . '.' . $file->extension, 150, 150)
->save(YII_PATH_PUBLIC . $catalog_h150 . '/' . $file->baseName . '.' . $file->extension, ['quality' => 80]);
// Инициализация
$this->imgs ?? $this->imgs = [];
// Запись в базу данных
$this->imgs = array_merge(
$this->imgs,
[[
'covr' => count($this->imgs) === 0 ? true : false,
'orig' => $catalog . '/' . $file->baseName . '.' . $file->extension,
'h150' => $catalog_h150 . '/' . $file->baseName . '.' . $file->extension
]]
);
$this->scenario = self::SCENARIO_WRITE;
if ($this->save()) {
// Изменения сохранены в базе данных
// Постинкрементация счётчика
$amount++;
}
} }
} }
// Запись if ($this->hasErrors()) {
$product->imgs = $buffer; // Получены ошибки
if ($product->save()) { foreach ($this->getErrors() as $attribute => $errors) {
// Товар обновлён // Перебор атрибутов
$return['main'] = $this->renderPartial('index', ['model' => $product]); foreach ($errors as $error) {
// Перебор ошибок атрибутов
$label = $this->getAttributeLabel($attribute);
Notification::_write("$label: $error", type: Notification::TYPE_ERROR);
} }
} }
}
return $amount;
}
/** /**
* Конец алгоритма * Импорт товаров
*
* На данный момент обрабатывает только импорт из
* файлов с расширением .excel
*/ */
end: public function importExcel(): bool
{
// Инициализация
$data = [];
$amount = 0;
if (yii::$app->request->isPost) { if ($this->validate()) {
// POST-запрос foreach ($this->file_excel as $file) {
// Перебор файлов
yii::$app->response->format = Response::FORMAT_JSON; // Инициализация
$dir = '../assets/import/' . date('Y_m_d#H-i', time()) . '/excel/';
return $return; // Сохранение на диск
if (!file_exists($dir)) {
mkdir($dir, 0775, true);
}
$file->saveAs($path = $dir . $file->baseName . '.' . $file->extension);
$data[] = Excel::import($path, [
'setFirstRecordAsKeys' => true,
'setIndexSheetByName' => true,
]);
} }
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model')); foreach ($data as $data) {
// Перебор конвертированных файлов
if (count($data) < 1) {
// Не найдены строки с товарами
$this->addError('erros', 'Не удалось найти данные товаров');
} else { } else {
return $this->redirect('/'); // Перебор найденных товаров
foreach ($data as $doc) {
// Перебор полученных документов
// Сохранение в базе данных
$product = new static($doc);
$product->scenario = $product::SCENARIO_WRITE;
if ($product->validate()) {
// Проверка пройдена
// Запись документа
$product->save();
// Постинкрементация счётчика
$amount++;
// Запись группы
// $group = static::class . 'Group';
// (new $group())->writeMember($product, $this->group);
} else {
// Проверка не пройдена
foreach ($product->errors as $attribute => $error) {
$this->addError($attribute, $error);
} }
} }
} }
}
}
// Деинициализация
$this->file_excel = '';
static::afterImportExcel($amount);
return true;
}
$this->addError('erros', 'Неизвестная ошибка');
static::afterImportExcel($amount);
return false;
}
/**
* Поиск по каталожному номеру
*
* Ищет продукт и возвращает его,
* либо выполняет поиск через представление
*
* @todo Переделать нормально
*/
public static function searchByCatn(string $catn, int $limit = 1, array $select = []): static|array|null
{
if ($limit <= 1) {
return static::findOne(['catn' => $catn]);
}
$query = self::find()
->where(['catn' => $catn])
->limit($limit)
->select($select)
->createCommand()
->execute()
->getAll();
foreach ($query as &$attribute) {
// Приведение всех свойств в массив и очистка от лишних данных
$attribute = $attribute->getAll();
}
return $query;
}
/**
* Поиск по каталожному номеру (через представления)
*
* Ищет продукт и возвращает его,
* либо выполняет поиск через представление
*
* @todo Переделать нормально
*/
public static function searchByPartialCatn(string $catn, int $limit = 1, array $select = []): static|array|null
{
$query = self::find()
->for('product')
->in('product_search')
->search(['catn' => $catn])
->limit($limit)
->select($select)
->createCommand()
->execute()
->getAll();
foreach ($query as &$attribute) {
// Приведение всех свойств в массив и очистка от лишних данных
$attribute = $attribute->getAll();
}
return $query;
}
/**
* Вызывается после загрузки поставок из excel-документа
*
* @param int $amount Количество
*/
public static function afterImportExcel(int $amount = 0): bool
{
// Инициализация
$model = new Notification;
$date = date('H:i d.m.Y', time());
// Настройка
$model->text = yii::$app->controller->renderPartial('/notification/system/afterImportExcel', compact('amount', 'date'));
$model->type = $model::TYPE_NOTICE;
// Отправка
return (bool) $model->write();
}
/**
* Вызывается после загрузки поставок из 1С
*
* @param int $amount Количество
*/
public static function afterImport1c(): bool
{
// Инициализация
$model = new Notification;
$date = date('H:i d.m.Y', time());
// Настройка
$model->text = yii::$app->controller->renderPartial('/notification/system/afterImport1c', compact('amount', 'date'));
$model->type = $model::TYPE_NOTICE;
// Отправка
return (bool) $model->write();
}
}

View File

@ -170,7 +170,7 @@ class Supply extends Product implements ProductInterface
$models = self::searchByAccount($account->readId()); $models = self::searchByAccount($account->readId());
$properties = self::xml2array($properties->xml); $properties = self::xml2array($properties->xml);
$account->on(ApiController::EVENT_AFTER_OFFER_SYNC, self::afterImport()); $account->on(ApiController::EVENT_AFTER_OFFER_SYNC, self::afterImport1c());
foreach ($models as $model) { foreach ($models as $model) {
// Перебор записей // Перебор записей