diff --git a/mirzaev/skillparts/system/assets/AppAsset.php b/mirzaev/skillparts/system/assets/AppAsset.php index acab48f..c801b8d 100644 --- a/mirzaev/skillparts/system/assets/AppAsset.php +++ b/mirzaev/skillparts/system/assets/AppAsset.php @@ -38,6 +38,7 @@ class AppAsset extends AssetBundle 'https://cdn.jsdelivr.net/bxslider/4.1.1/jquery.bxslider.min.js', 'https://unpkg.com/cookielib/src/cookie.min.js', 'https://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU', + 'js/moment.min.js', 'js/menu.js', 'js/main.js', 'js/account.js', diff --git a/mirzaev/skillparts/system/controllers/AuthenticationController.php b/mirzaev/skillparts/system/controllers/AuthenticationController.php index 5bbc015..cc0a3c8 100644 --- a/mirzaev/skillparts/system/controllers/AuthenticationController.php +++ b/mirzaev/skillparts/system/controllers/AuthenticationController.php @@ -39,8 +39,16 @@ class AuthenticationController extends Controller // Инициализация $model = new AccountForm(yii::$app->request->post('AccountForm')); $type = yii::$app->request->post('type') ?? yii::$app->request->get('type'); + $target = yii::$app->request->post('target') ?? yii::$app->request->get('target'); $model->scenario = $model::SCENARIO_AUTHENTICATION; + // Фильтрация + $target = match ($target) { + 'menu' => 'menu', + 'main' => 'main', + default => 'main' + }; + if (yii::$app->request->isPost) { // AJAX-POST-запрос @@ -48,13 +56,13 @@ class AuthenticationController extends Controller yii::$app->response->format = Response::FORMAT_JSON; // Валидация формы - if (!empty($errors = ActiveForm::validate($model))) { + // if (!empty($errors = ActiveForm::validate($model))) { - // Настройка кода ответа - yii::$app->response->statusCode = 401; + // // Настройка кода ответа + // yii::$app->response->statusCode = 401; - return $errors; - }; + // return $errors; + // }; if (!yii::$app->user->isGuest || $model->authentication()) { // Аккаунт аутентифицирован @@ -69,7 +77,7 @@ class AuthenticationController extends Controller // Запись ответа $return = [ - 'menu' => $this->renderPartial('/account/panel/authenticated', compact( + $target => $this->renderPartial('/account/panel/authenticated', compact( 'notifications_button', 'notifications_panel', 'notifications_panel_full' @@ -123,7 +131,7 @@ class AuthenticationController extends Controller yii::$app->response->statusCode = 400; return [ - 'main' => $this->renderPartial('/account/index', compact('model')), + $target => $this->renderPartial('/account/index', compact('model')), 'redirect' => '/authentication', '_csrf' => yii::$app->request->getCsrfToken() ]; diff --git a/mirzaev/skillparts/system/controllers/InvoiceController.php b/mirzaev/skillparts/system/controllers/InvoiceController.php index 192f73b..876ef89 100644 --- a/mirzaev/skillparts/system/controllers/InvoiceController.php +++ b/mirzaev/skillparts/system/controllers/InvoiceController.php @@ -30,7 +30,17 @@ class InvoiceController extends Controller // Инициализация файла $file = YII_PATH_PUBLIC . '/../assets/invoices/' . $order . '/invoice.xlsx'; - if (file_exists($file)) return $this->response->sendFile($file); - else yii::$app->response->statusCode = 500; + if (file_exists($file)) { + // Удалось найти файл + + return $this->response->sendFile($file); + } else { + // Не удалось найти файл + + // Запись кода ответа + yii::$app->response->statusCode = 500; + + return yii::$app->response->redirect('/orders'); + } } } diff --git a/mirzaev/skillparts/system/controllers/OrderController.php b/mirzaev/skillparts/system/controllers/OrderController.php index e2d019f..cb6dd3c 100644 --- a/mirzaev/skillparts/system/controllers/OrderController.php +++ b/mirzaev/skillparts/system/controllers/OrderController.php @@ -18,11 +18,13 @@ use app\models\AccountEdgeOrder; use app\models\connection\Dellin; use app\models\Invoice; use app\models\Notification; +use app\models\Settings; use app\models\SupplyEdgeProduct; use Codeception\PHPUnit\ResultPrinter\HTML; use DateTime; use Exception; +use Throwable; class OrderController extends Controller { @@ -38,7 +40,6 @@ class OrderController extends Controller 'actions' => [ 'index', 'accept', - 'read', 'write', 'delete', 'amount-update', @@ -62,13 +63,23 @@ class OrderController extends Controller public function accessDenied() { - // Инициализация + // Инициализация cookie $cookies = yii::$app->response->cookies; + // Инициализация данных из HTTP-заголовка referrer + $referrer = trim(parse_url(yii::$app->request->referrer, PHP_URL_PATH) . '?' . parse_url(yii::$app->request->referrer, PHP_URL_QUERY), '/'); + + $redirect = match(yii::$app->request->pathInfo) { + 'order/write' => $referrer, + 'order/delete', 'order/amount-update', 'order/request' => 'cart', + 'order/accept', 'order/supply-read', 'order/supply-write-stts', 'order/supply-edit-time', 'order/supply-edit-cost', 'order/supply-edit-comm' => 'orders', + default => yii::$app->request->pathInfo + }; + // Запись cookie с редиректом, который выполнится после авторизации $cookies->add(new Cookie([ 'name' => 'redirect', - 'value' => yii::$app->request->pathInfo + 'value' => $redirect ])); if (yii::$app->request->isPost) { @@ -90,11 +101,100 @@ class OrderController extends Controller } } - public function actionIndex(string $type = 'all') + public function actionIndex(string $filter = 'all') { - // Инициализация - $orders = Order::search(type: $type, limit: 10, page: 1, select: '{account_edge_order, order}', supplies: true); - $moderator_orders = self::genOrdersForModeration(); + // Инициализация cookie + $cookies = yii::$app->response->cookies; + + // Инициализация фильтра по типу + if ($filter === 'last') { + // Запрошено использование прошлых данных о фильтрации по типу + + // Чтение сохраненных cookie с данными прошлой фильтрации по типу + $filter = $cookies->get('filter'); + } else { + // Запрошена сортировка + + // Запись cookie с данными текущей фильтрации по типу + $cookies->add(new Cookie([ + 'name' => 'filter', + 'value' => $filter + ])); + } + + // Инициализация входных данных + $from = yii::$app->request->post('from') ?? yii::$app->request->get('from'); + $to = yii::$app->request->post('to') ?? yii::$app->request->get('to'); + + // Инициализация фильтра по периоду + if (!empty($from) && !empty($to) && filter_var($from, FILTER_VALIDATE_INT) && filter_var($to, FILTER_VALIDATE_INT)) { + // Данные фильтра по периоду переданы и представляют собой целые числа (подразумевается unixtime) + + // Конвертация типов + $from = (int) $from; + $to = (int) $to; + + // Запись cookie с данными начала периода + $cookies->add(new Cookie([ + 'name' => 'from', + 'value' => $from + ])); + + // Запись cookie с данными окончания периода + $cookies->add(new Cookie([ + 'name' => 'to', + 'value' => $to + ])); + } else { + // Некоректные данные или их отсутствие + + // Чтение сохраненных cookie с данными прошлой фильтрации по периоду (начало) + $from = $cookies->get('from'); + + // Чтение сохраненных cookie с данными прошлой фильтрации по периоду (конец) + $to = $cookies->get('to'); + + if (empty($from) || empty($to) || filter_var($from, FILTER_VALIDATE_INT) || filter_var($to, FILTER_VALIDATE_INT)) { + // Каких-то cookie не оказалось, либо данные в них не подходящие, либо это первый запрос, но без фильтрации по периоду + + // Реинициализация на стандартные параметры (неделя) + $from = time() - 604800; + $to = time(); + } else { + // Данные в cookie найдены и подходят для выполнения + + // Конвертация типов + $from = (int) $from; + $to = (int) $to; + } + } + + // Инициализация заказов + $orders = Order::search( + type: $filter, + limit: 10, + page: 1, + select: '{account_edge_order, order}', + supplies: true, + from: $from, + to: $to + ); + + if ( + !yii::$app->user->isGuest + && yii::$app->user->identity->type === 'administrator' + || yii::$app->user->identity->type === 'moderator' + ) { + // Пользователь имеет доступ + + // Инициализация заказов для модератора + $moderator_orders = self::genOrdersForModeration(); + } else { + // Пользователь не имеет доступ + + // Инициализация заглушки + $moderator_orders = null; + } if (yii::$app->request->isPost) { // POST-запрос @@ -102,8 +202,12 @@ class OrderController extends Controller // Настройка yii::$app->response->format = Response::FORMAT_JSON; + // Конвертация из UNIXTIME в формат поддерживаемый календарём по спецификации HTML + $from = DateTime::createFromFormat('U', (string) $from)->format('Y-m-d'); + $to = DateTime::createFromFormat('U', (string) $to)->format('Y-m-d'); + return [ - 'main' => $this->renderPartial('/orders/index', compact('orders', 'moderator_orders')), + 'main' => $this->renderPartial('/orders/index', compact('orders', 'moderator_orders', 'from', 'to')), 'title' => 'Заказы', 'redirect' => '/orders', '_csrf' => yii::$app->request->getCsrfToken() @@ -382,6 +486,9 @@ class OrderController extends Controller if ($edge->update()) { // Удалось сохранить изменения + // Запись в журнал + $model->journal('requested'); + // Инициализация буфера поставок $supplies = []; @@ -412,31 +519,11 @@ class OrderController extends Controller ] ])); - // Запись в журнал - $model->journal('requested'); - // Отправка уведомлений модераторам Notification::_write($this->renderPartial('/notification/system/orders/new', ['id' => $edge->_key]), true, '@auth', Notification::TYPE_MODERATOR_ORDER_NEW); } - // Инициализация - $orders = Order::search(type: 'all', limit: 10, page: 1, select: '{account_edge_order, order}', supplies: true); - $moderator_orders = self::genOrdersForModeration(); - - if (yii::$app->request->isPost) { - // POST-запрос - - yii::$app->response->format = Response::FORMAT_JSON; - - return [ - 'main' => $this->renderPartial('/orders/index', compact('orders', 'moderator_orders')), - 'title' => 'Заказы', - 'redirect' => '/orders', - '_csrf' => yii::$app->request->getCsrfToken() - ]; - } - - return $this->render('/orders/index', compact('orders', 'moderator_orders')); + return $this->actionIndex(); } /** @@ -486,10 +573,23 @@ class OrderController extends Controller $product = Product::searchBySupplyId($order_edge_supply->_to); try { + // Инициализация данных геолокации + try { + $from = (int) $account['opts']['delivery_from_terminal'] ?? Settings::search()->delivery_from_default ?? 36; + } catch (Exception $e) { + $from = (int) Settings::search()->delivery_from_default ?? 36; + } + + try { + $to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36; + } catch (Exception $e) { + $to = 36; + } + // Инициализация доставки $delivery = Dellin::calcDeliveryAdvanced( - $account['opts']['delivery_from_terminal'], - yii::$app->user->identity->opts['delivery_to_terminal'], + $from, + $to, (int) ($product['wght'] ?? 0), (int) ($product['dmns']['x'] ?? 0), (int) ($product['dmns']['y'] ?? 0), @@ -497,13 +597,30 @@ class OrderController extends Controller avia: $order_edge_supply->dlvr['type'] === 'avia' ); - // Рассчет времени + // Инициализация даты отправки try { + // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель) + + $delivery_send_date = DateTime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspSender'])->getTimestamp(); + } catch (Throwable $e) { + // Взять данные из "pickup" (Дата передачи груза на адресе отправителя) + + $delivery_send_date = DateTime::createFromFormat('Y-m-d', $delivery['orderDates']['pickup'])->getTimestamp(); + } + + // Инициализация времени доставки + try { + // Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале) + $delivery_converted = DateTime::createFromFormat('Y-m-d H:i:s', $delivery['orderDates']['giveoutFromOspReceiver'])->getTimestamp(); - } catch (Exception $e) { + } catch (Throwable $e) { + // Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель) + $delivery_converted = DateTime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspReceiver'])->getTimestamp(); } - $delivery = ceil(($delivery_converted - time()) / 60 / 60 / 24) + 1; + + // Рассчет времени доставки + $delivery = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24) + 1; } catch (Exception $e) { // var_dump($e->getMessage()); // var_dump($e->getTrace()); diff --git a/mirzaev/skillparts/system/controllers/ProductController.php b/mirzaev/skillparts/system/controllers/ProductController.php index 4d9c9e6..cb35b7d 100644 --- a/mirzaev/skillparts/system/controllers/ProductController.php +++ b/mirzaev/skillparts/system/controllers/ProductController.php @@ -115,6 +115,7 @@ class ProductController extends Controller // Товар обновлён $return['main'] = $this->renderPartial('index', compact('model')); + $return['redirect'] = '/product/' . $model->catn; } } @@ -131,10 +132,16 @@ class ProductController extends Controller return $return; } - if ($model = Product::searchByCatn($catn)) { - return $this->render('index', compact('model')); + if (Product::searchByCatn($catn)) { + // Старый товар ещё существует (подразумевается, что произошла ошибка) + + // Возврат на страницу товара + return $this->redirect("/product/$catn"); } else { - return $this->redirect('/'); + // Старый товар не существует (подразумевается, что его артикул успешно изменён) + + // Переадресация на новую страницу товара + return $this->redirect("/product/$model->catn"); } } diff --git a/mirzaev/skillparts/system/controllers/ProfileController.php b/mirzaev/skillparts/system/controllers/ProfileController.php index 66c39b5..56b0543 100644 --- a/mirzaev/skillparts/system/controllers/ProfileController.php +++ b/mirzaev/skillparts/system/controllers/ProfileController.php @@ -29,6 +29,12 @@ class ProfileController extends Controller 'access' => [ 'class' => AccessControl::class, 'rules' => [ + [ + 'allow' => true, + 'actions' => [ + 'geolocation-init' + ] + ], [ 'allow' => true, 'roles' => ['@'], @@ -37,8 +43,7 @@ class ProfileController extends Controller 'supplies', 'import', 'monitoring', - 'readGroups', - 'geolocation-init' + 'readGroups' ] ], [ @@ -468,8 +473,21 @@ class ProfileController extends Controller { if (Yii::$app->request->isPost) { // POST-запрос - // Инициализация аккаунта - $account ?? $account = yii::$app->user->identity; + + if (is_null($account)) { + // Данные аккаунта не переданы + + if (yii::$app->user->isGuest) { + // Аккаунт не аутентифицирован + + return false; + } else { + // Аккаунт аутентифицирован + + // Инициализация + $account = yii::$app->user->identity; + } + } // Инициализация IP-адреса $ip = yii::$app->request->userIp === 'localhost' || yii::$app->request->userIp === '127.0.0.1' ? '46.226.227.20' : yii::$app->request->userIp; @@ -509,6 +527,6 @@ class ProfileController extends Controller return false; } - yii::$app->response->redirect('/'); + return false; } } diff --git a/mirzaev/skillparts/system/controllers/VerifyController.php b/mirzaev/skillparts/system/controllers/VerifyController.php index 1a945e8..c42370c 100644 --- a/mirzaev/skillparts/system/controllers/VerifyController.php +++ b/mirzaev/skillparts/system/controllers/VerifyController.php @@ -19,14 +19,57 @@ class VerifyController extends Controller if (Account::verification($vrfy, auth: true)) { // Успешно подтверждена регистрация - return $this->redirect('/'); + return $this->redirect('/profile'); } return ErrorController::throw('Ошибка подтверждения регистрации', 'Код подтверждения не совпадает с тем, что мы отправили вам на почту, либо регистрация уже была подтверждена. Свяжитесь с администрацией'); } else { // Простой запрос - return $this->render('/account/verify'); + if (yii::$app->user->isGuest) { + // Пользователь не аутентифицирован + + return yii::$app->response->redirect('/registration'); + } else { + // Пользователь аутентифицирован + + if (yii::$app->user->identity->vrfy === true) { + // Регистрация аккаунта уже подтверждена + + if (yii::$app->request->isPost) { + // POST-запрос + + // Запись формата ответа + yii::$app->response->format = Response::FORMAT_JSON; + + return [ + 'main' => $this->renderPartial('/profile/index'), + 'title' => 'Профиль', + 'redirect' => '/profile', + '_csrf' => yii::$app->request->getCsrfToken() + ]; + } else { + // GET-запрос (подразумевается) + + return $this->render('/profile/index'); + } + } else { + // Регистрация аккаунта ещё не подтверждена + + if (yii::$app->request->isPost) { + // POST-запрос + + // Запись формата ответа + yii::$app->response->format = Response::FORMAT_JSON; + + return $this->genPostVerify(); + } else { + // GET-запрос (подразумевается) + + return $this->render('/account/verify'); + } + } + } } } @@ -37,24 +80,39 @@ class VerifyController extends Controller */ public function actionSend(): string|array { - yii::$app->user->identity->verifyRegenerate(); + if (!yii::$app->user->isGuest) { + // Пользователь аутентифицирован - yii::$app->user->identity->sendMailVerify(); + // Регенерация кода подтверждения + yii::$app->user->identity->verifyRegenerate(); - if (yii::$app->request->isPost) { - // POST-запрос + // Отправка кода подтверждения на почту + yii::$app->user->identity->sendMailVerify(); - yii::$app->response->format = Response::FORMAT_JSON; + if (yii::$app->request->isPost) { + // POST-запрос - return [ - 'main' => $this->renderPartial('/account/verify'), - 'title' => 'Корзина', - '_csrf' => yii::$app->request->getCsrfToken() - ]; - } else { - // Подразумевается как GET-запрос + // Запись формата ответа + yii::$app->response->format = Response::FORMAT_JSON; - return $this->render('/account/verify'); + return $this->genPostVerify(); + } else { + // GET-запрос (подразумевается) + + return $this->render('/account/verify'); + } } } + + /** + * Генерация данных для POST-запроса с переадресацией на страницу подтверждения + */ + function genPostVerify(): array { + return [ + 'main' => $this->renderPartial('/account/verify'), + 'title' => 'Подтверждение аккаунта', + 'redirect' => '/account/verify', + '_csrf' => yii::$app->request->getCsrfToken() + ]; + } } diff --git a/mirzaev/skillparts/system/models/Account.php b/mirzaev/skillparts/system/models/Account.php index 0cdcbce..1b8abd9 100644 --- a/mirzaev/skillparts/system/models/Account.php +++ b/mirzaev/skillparts/system/models/Account.php @@ -259,6 +259,53 @@ class Account extends Document implements IdentityInterface, PartnerInterface return static::find()->where(['type' => 'moderator'])->orWhere(['type' => 'administrator'])->all(); } + /** + * Подтверждение регистрации + */ + public static function verification(string $vrfy, bool $auth = false): bool + { + if ($account = static::findByVrfy($vrfy)) { + // Аккаунт найден + + // Запись в буфер + $account->vrfy = true; + + // Отправка изменений + $updated = $account->update() > 0; + + if ($updated && $auth) { + // Регистрация была подтверждена, а так же запрошена автоматическая аутентификация + + // Аутентификация + yii::$app->user->login($account, true ? 3600 * 24 * 30 : 0); + } + + return $updated; + } + + return false; + } + + /** + * Проверка пароля + */ + public function validatePassword(string $pswd): bool + { + // return yii::$app->security->validatePassword($pswd, $this->pswd); + return $pswd === $this->pswd; + } + + /** + * Проверка аутентификационного ключа + * + * @todo Подождать обновление Yii2 и добавить + * проверку типов передаваемых параметров + */ + public function validateAuthKey($auth): bool + { + return $this->getAuthKey() === $auth; + } + /** * Проверка почты */ @@ -311,53 +358,6 @@ class Account extends Document implements IdentityInterface, PartnerInterface } } - /** - * Подтверждение регистрации - */ - public static function verification(string $vrfy, bool $auth = false): bool - { - if ($account = static::findByVrfy($vrfy)) { - // Аккаунт найден - - // Запись в буфер - $account->vrfy = true; - - // Отправка изменений - $updated = $account->update() > 0; - - if ($updated && $auth) { - // Регистрация была подтверждена, а так же запрошена автоматическая аутентификация - - // Аутентификация - yii::$app->user->login($account, true ? 3600 * 24 * 30 : 0); - } - - return $updated; - } - - return false; - } - - /** - * Проверка пароля - */ - public function validatePassword(string $pswd): bool - { - // return yii::$app->security->validatePassword($pswd, $this->pswd); - return $pswd === $this->pswd; - } - - /** - * Проверка аутентификационного ключа - * - * @todo Подождать обновление Yii2 и добавить - * проверку типов передаваемых параметров - */ - public function validateAuthKey($auth): bool - { - return $this->getAuthKey() === $auth; - } - /** * Записать параметр * diff --git a/mirzaev/skillparts/system/models/Order.php b/mirzaev/skillparts/system/models/Order.php index a115185..6af90b8 100644 --- a/mirzaev/skillparts/system/models/Order.php +++ b/mirzaev/skillparts/system/models/Order.php @@ -237,7 +237,7 @@ class Order extends Document implements DocumentInterface * * @todo Привести в порядок */ - public static function search(Account|string $account = null, string $type = 'current', int $limit = 1, int $page = 1, string $select = null, bool $supplies = false): self|array|null + public static function search(Account|string $account = null, string $type = 'current', int $limit = 1, int $page = 1, string $select = null, bool $supplies = false, int|null $from = null, int|null $to = null): self|array|null { // Инициализация аккаунта if (empty($account) && isset(yii::$app->user->identity)) { @@ -276,13 +276,35 @@ class Order extends Document implements DocumentInterface // Инициализация сдвига по запрашиваемым данным (пагинация) $offset = $limit * ($page - 1); - // Запрос + // Инициализация фильтрации + if (isset($from, $to)) { + // Задан период + + // Инициализация логики + $foreach = [ + ['edge' => 'account_edge_order'], + ['jrnl' => 'order.jrnl'] + ]; + + // Инициализация фильтра + $where = "edge._to == order._id && jrnl.action == 'requested' && jrnl.date >= $from && jrnl.date <= $to"; + } else { + // Ничего не задано + + // Инициализация логики + $foreach = ['edge' => 'account_edge_order']; + + // Инициализация фильтра + $where = "edge._to == order._id"; + } + + // Запрос на поиск заказов $return = self::searchByEdge( from: 'account', to: 'order', subquery_where: $subquery_where, - foreach: ['edge' => 'account_edge_order'], - where: 'edge._to == order._id', + foreach: $foreach, + where: $where, limit: $limit, offset: $offset, sort: ['DESC'], diff --git a/mirzaev/skillparts/system/views/account/index.php b/mirzaev/skillparts/system/views/account/index.php index 6dc3ed7..9a48e40 100644 --- a/mirzaev/skillparts/system/views/account/index.php +++ b/mirzaev/skillparts/system/views/account/index.php @@ -17,12 +17,21 @@ use app\models\AccountForm;