diff --git a/mirzaev/skillparts/system/controllers/CartController.php b/mirzaev/skillparts/system/controllers/CartController.php index bd1b797..0bd79e4 100644 --- a/mirzaev/skillparts/system/controllers/CartController.php +++ b/mirzaev/skillparts/system/controllers/CartController.php @@ -10,13 +10,68 @@ use yii\filters\AccessControl; use yii\web\Controller; use yii\web\Response; +use app\models\Product; use app\models\Order; use app\models\OrderEdgeSupply; use Exception; +use yii\web\Cookie; class CartController extends Controller { + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::class, + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['@'], + 'actions' => [ + 'index', + 'edit-comm' + ] + ], + [ + 'allow' => false, + 'roles' => ['?'], + 'denyCallback' => [$this, 'accessDenied'] + ] + ] + ] + ]; + } + + public function accessDenied() + { + // Инициализация + $cookies = yii::$app->response->cookies; + + // Запись cookie с редиректом, который выполнится после авторизации + $cookies->add(new Cookie([ + 'name' => 'redirect', + 'value' => yii::$app->request->pathInfo + ])); + + if (yii::$app->request->isPost) { + // POST-запрос + + // Настройка + yii::$app->response->format = Response::FORMAT_JSON; + + // Генерация ответа + yii::$app->response->content = json_encode([ + 'main' => $this->renderPartial('/account/index'), + 'redirect' => '/cart', + '_csrf' => yii::$app->request->getCsrfToken() + ]); + } else if (yii::$app->request->isGet) { + // GET-запрос + + $this->redirect('/authentication'); + } + } /** * Страница: "Корзина" * diff --git a/mirzaev/skillparts/system/controllers/ErrorController.php b/mirzaev/skillparts/system/controllers/ErrorController.php index 684b353..10943f1 100644 --- a/mirzaev/skillparts/system/controllers/ErrorController.php +++ b/mirzaev/skillparts/system/controllers/ErrorController.php @@ -7,7 +7,7 @@ use yii\web\Controller; class ErrorController extends Controller { - public function actionIndex() + public function actionIndex(): string { $exception = Yii::$app->errorHandler->exception; @@ -15,21 +15,26 @@ class ErrorController extends Controller // Исключение не выброшено // Запись кода ошибки - $statusCode = $exception->statusCode ?? $exception->getCode() ?? 0; + $code = $exception->statusCode ?? $exception->getCode() ?? 0; // Запись названия ошибки - $name = match ($statusCode) { + $title = match ($code) { 404 => '404 (Не найдено)', default => $exception->getName() }; // Запись сообщения об ошибке - $message = match ($statusCode) { + $description = match ($code) { 404 => 'Страница не найдена', default => $exception->getMessage() }; - return $this->render('/error', compact('exception', 'statusCode', 'name', 'message')); + return $this->render('/error', compact('exception', 'code', 'title', 'description')); } } + + public static function throw(string $title, string $description): string { + + return yii::$app->controller->render('/error', compact('title', 'description')); + } } diff --git a/mirzaev/skillparts/system/controllers/OfferController.php b/mirzaev/skillparts/system/controllers/OfferController.php index 412baf7..9cf748b 100644 --- a/mirzaev/skillparts/system/controllers/OfferController.php +++ b/mirzaev/skillparts/system/controllers/OfferController.php @@ -44,16 +44,16 @@ class OfferController extends Controller 'value' => yii::$app->request->pathInfo ])); - if (Yii::$app->request->isPost) { + if (yii::$app->request->isPost) { // POST-запрос // Настройка - Yii::$app->response->format = Response::FORMAT_JSON; + yii::$app->response->format = Response::FORMAT_JSON; // Генерация ответа - Yii::$app->response->content = json_encode([ + yii::$app->response->content = json_encode([ 'redirect' => '/authentication', - '_csrf' => Yii::$app->request->getCsrfToken() + '_csrf' => yii::$app->request->getCsrfToken() ]); } else if (Yii::$app->request->isGet) { // GET-запрос diff --git a/mirzaev/skillparts/system/controllers/OrderController.php b/mirzaev/skillparts/system/controllers/OrderController.php index 010c0a7..058aa8c 100644 --- a/mirzaev/skillparts/system/controllers/OrderController.php +++ b/mirzaev/skillparts/system/controllers/OrderController.php @@ -42,7 +42,7 @@ class OrderController extends Controller 'write', 'delete', 'amount-update', - 'pay', + 'request', 'supply-read', 'supply-write-stts', 'supply-edit-time', @@ -71,19 +71,19 @@ class OrderController extends Controller 'value' => yii::$app->request->pathInfo ])); - if (Yii::$app->request->isPost) { + if (yii::$app->request->isPost) { // POST-запрос // Настройка - Yii::$app->response->format = Response::FORMAT_JSON; + yii::$app->response->format = Response::FORMAT_JSON; // Генерация ответа - Yii::$app->response->content = json_encode([ - 'main' => $this->renderPartial('/orders/index'), - 'redirect' => '/authentication', - '_csrf' => Yii::$app->request->getCsrfToken() + yii::$app->response->content = json_encode([ + 'main' => $this->renderPartial('/account/index'), + 'redirect' => '/order', + '_csrf' => yii::$app->request->getCsrfToken() ]); - } else if (Yii::$app->request->isGet) { + } else if (yii::$app->request->isGet) { // GET-запрос $this->redirect('/authentication'); @@ -357,9 +357,9 @@ class OrderController extends Controller } /** - * Оплата + * Запросить заказ */ - public function actionPay(): string|array|null + public function actionRequest(): string|array|null { // Инициализация $model = Order::search(supplies: true); diff --git a/mirzaev/skillparts/system/controllers/RegistrationController.php b/mirzaev/skillparts/system/controllers/RegistrationController.php index caaaa59..b3f2f91 100644 --- a/mirzaev/skillparts/system/controllers/RegistrationController.php +++ b/mirzaev/skillparts/system/controllers/RegistrationController.php @@ -32,9 +32,24 @@ class RegistrationController extends Controller if ($type === 'registration' && (!yii::$app->user->isGuest || $model->registration())) { // Данные прошли проверку и аккаунт был создан + echo 1; die; + // Аутентификация $model->scenario = $model::SCENARIO_AUTHENTICATION; - $model->authentication(); + + if (!$model->authentication()) { + // Не удалось аутентифицироваться + + yii::$app->response->statusCode = 401; + + $model->scenario = $model::SCENARIO_REGISTRATION; + + return [ + 'main' => $this->renderPartial('/account/index', compact('model') + ['registration' => true]), + 'redirect' => '/registration', + '_csrf' => yii::$app->request->getCsrfToken() + ]; + } // Инициализация $notifications_button = $this->renderPartial('/notification/button'); diff --git a/mirzaev/skillparts/system/controllers/SearchController.php b/mirzaev/skillparts/system/controllers/SearchController.php index fddc09f..da9b6e6 100644 --- a/mirzaev/skillparts/system/controllers/SearchController.php +++ b/mirzaev/skillparts/system/controllers/SearchController.php @@ -14,7 +14,7 @@ use app\models\Supply; use app\models\Search; use app\models\connection\Dellin; - +use app\models\Settings; use Exception; class SearchController extends Controller @@ -181,23 +181,62 @@ class SearchController extends Controller // Инициализация аккаунта $connection['account'] = Account::searchBySupplyId($connection['supply_edge_product'][0]['_from']); + // Инициализация продукта + $connection['product'] = Product::searchBySupplyId($connection['supply_edge_product'][0]['_from']); + try { - // Инициализация доставки (автоматическая) - $connection['delivery'] = Dellin::calcDeliveryAdvanced( - $connection['account']['opts']['delivery_from_terminal'], - yii::$app->user->identity->opts['delivery_to_terminal'], - (int) ($connection['product']['wght'] ?? 0), - (int) ($connection['product']['dmns']['x'] ?? 0), - (int) ($connection['product']['dmns']['y'] ?? 0), - (int) ($connection['product']['dmns']['z'] ?? 0) - ); - $connection['delivery']['type'] = 'auto'; + // Инициализация данных геолокации + try { + $from = (int) $connection['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; + } + + if ($buffer_connection = $connection['product']['bffr']["$from-$to"] ?? false) { + // Найдены данные доставки в буфере + + if (time() < $buffer_connection['expires']) { + // Срок хранения не превышен, информация актуальна + + // Запись в буфер вывода + $connection['delivery'] = $buffer_connection['data']; + $connection['delivery']['type'] = 'auto'; + } + } else { + // Инициализация инстанции продукта в базе данных + $product = Product::searchByCatn($connection['product']['catn']); + + // Инициализация доставки Dellin (автоматическая) + $product->bffr = ($product->bffr ?? []) + [ + "$from-$to" => [ + 'data' => $connection['delivery'] = Dellin::calcDeliveryAdvanced( + $from, + $to, + (int) ($connection['product']['wght'] ?? 0), + (int) ($connection['product']['dmns']['x'] ?? 0), + (int) ($connection['product']['dmns']['y'] ?? 0), + (int) ($connection['product']['dmns']['z'] ?? 0) + ), + 'expires' => time() + 86400 + ] + ]; + $connection['delivery']['type'] = 'auto'; + + // Отправка в базу данных + $product->update(); + } } catch (Exception $e) { $connection['delivery']['error'] = true; - // var_dump($e->getMessage()); - // var_dump($e->getTrace()); - // var_dump($e->getFile()); + var_dump($e->getMessage()); + var_dump($e->getTrace()); + var_dump($e->getFile()); // var_dump(json_decode($e->getMessage(), true)['errors']); // die; @@ -209,18 +248,53 @@ class SearchController extends Controller // Инициализация версии для рассчета доставки по воздуху $buffer = $connection; - try { - // Инициализация доставки Dellin (самолётом) - $buffer['delivery'] = Dellin::calcDeliveryAdvanced( - $buffer['account']['opts']['delivery_from_terminal'], - yii::$app->user->identity->opts['delivery_to_terminal'], - (int) ($buffer['product']['wght'] ?? 0), - (int) ($buffer['product']['dmns']['x'] ?? 0), - (int) ($buffer['product']['dmns']['y'] ?? 0), - (int) ($buffer['product']['dmns']['z'] ?? 0), - avia: true - ); - $buffer['delivery']['type'] = 'avia'; + try {// Инициализация данных геолокации + try { + $from = (int) $connection['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; + } + + if ($buffer_connection = $connection['product']['bffr']["$from-$to-avia"] ?? false) { + // Найдены данные доставки в буфере + + if (time() < $buffer_connection['expires']) { + // Срок хранения не превышен, информация актуальна + + // Запись в буфер вывода + $connection['delivery'] = $buffer_connection['data']; + $connection['delivery']['type'] = 'avia'; + } + } else { + // Инициализация инстанции продукта в базе данных + $product = Product::searchByCatn($connection['product']['catn']); + + // Инициализация доставки Dellin (автоматическая) + $product->bffr = ($product->bffr ?? []) + [ + "$from-$to-avia" => [ + 'data' => $connection['delivery'] = Dellin::calcDeliveryAdvanced( + $from, + $to, + (int) ($connection['product']['wght'] ?? 0), + (int) ($connection['product']['dmns']['x'] ?? 0), + (int) ($connection['product']['dmns']['y'] ?? 0), + (int) ($connection['product']['dmns']['z'] ?? 0), + avia: true + ), + 'expires' => time() + 86400 + ] + ]; + $connection['delivery']['type'] = 'avia'; + + // Отправка в базу данных + $product->update(); + } } catch (Exception $e) { $buffer['delivery']['error'] = true; diff --git a/mirzaev/skillparts/system/controllers/VerifyController.php b/mirzaev/skillparts/system/controllers/VerifyController.php new file mode 100644 index 0000000..a57c20c --- /dev/null +++ b/mirzaev/skillparts/system/controllers/VerifyController.php @@ -0,0 +1,60 @@ +redirect('/'); + } + + return ErrorController::throw('Ошибка подтверждения регистрации', 'Код подтверждения не совпадает с тем, что мы отправили вам на почту, либо регистрация уже была подтверждена. Свяжитесь с администрацией'); + } else { + // Простой запрос + + return $this->render('/account/verify'); + } + } + + /** + * Отправить запрос на активацию + * + * @return string + */ + public function actionSend(): string|array + { + yii::$app->user->identity->verifyRegenerate(); + + yii::$app->user->identity->sendMailVerify(); + + if (yii::$app->request->isPost) { + // POST-запрос + + yii::$app->response->format = Response::FORMAT_JSON; + + return [ + 'main' => $this->renderPartial('/account/verify'), + 'title' => 'Корзина', + '_csrf' => yii::$app->request->getCsrfToken() + ]; + } else { + // Подразумевается как GET-запрос + + return $this->render('/account/verify'); + } + } +} diff --git a/mirzaev/skillparts/system/models/Account.php b/mirzaev/skillparts/system/models/Account.php index 2b0c813..442cc3d 100644 --- a/mirzaev/skillparts/system/models/Account.php +++ b/mirzaev/skillparts/system/models/Account.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace app\models; -use Yii; +use yii; use yii\web\IdentityInterface; use carono\exchange1c\interfaces\PartnerInterface; @@ -50,6 +50,7 @@ class Account extends Document implements IdentityInterface, PartnerInterface 'opts', 'agnt', 'type', + 'vrfy', 'acpt' ] ); @@ -76,6 +77,7 @@ class Account extends Document implements IdentityInterface, PartnerInterface 'opts' => 'Параметры', 'agnt' => 'Агент (поставщик)', 'type' => 'Тип аккаунта', + 'vrfy' => 'Статус подтверждения владением почты', 'acpt' => 'Согласие с офертой' ] ); @@ -114,6 +116,10 @@ class Account extends Document implements IdentityInterface, PartnerInterface [ 'indx', 'string' + ], + [ + 'vrfy', + 'validateVrfyUnique' ] ] ); @@ -129,7 +135,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface { if (parent::beforeSave($data)) { if ($this->isNewRecord) { - $this->auth = Yii::$app->security->generateRandomString(); + $this->auth = yii::$app->security->generateRandomString(); + $this->vrfy = yii::$app->security->generateRandomString(); } return true; @@ -204,6 +211,17 @@ class Account extends Document implements IdentityInterface, PartnerInterface return static::findOne(['mail' => $mail]); } + /** + * Поиск по статусу активации почты + * + * @todo Подождать обновление Yii2 и добавить + * проверку типов передаваемых параметров + */ + public static function findByVrfy($vrfy): ?self + { + return static::findOne(['vrfy' => $vrfy]); + } + /** * Поиск по идентификатору * @@ -244,10 +262,66 @@ class Account extends Document implements IdentityInterface, PartnerInterface */ public static function validateMail(string $mail): bool { - if (static::findByMail($mail)) { + if ($account = static::findByMail($mail)) { // Почта найдена в базе данных - return true; + if (isset($account) && $account->vrfy === true) { + // Регистрация подтверждена + + return true; + } + } + + return false; + } + + /** + * Проверка статуса подтверждения регистрации + */ + public static function validateVrfy(string $vrfy): bool + { + if ($account = static::findByVrfy($vrfy)) { + // Статус найден в базе данных + + if ($account->vrfy === true) { + // Регистрация подтверждена + + return true; + } + } + + return false; + } + + /** + * Проверка данных для поля подтверждения регистрации + */ + public function validateVrfyUnique($attribute, $params) + { + if (!$this->hasErrors()) { + // Проблем нет + + if ($attribute !== true && $this->validateVrfy($attribute)) { + // Проверка не пройдена + + $this->addError($attribute, 'Ключ подтверждения регистрации уже используется'); + } + } + } + + /** + * Подтверждение регистрации + */ + public static function verification(string $vrfy): bool + { + if ($account = static::findByVrfy($vrfy)) { + // Аккаунт найден + + // Запись в буфер + $account->vrfy = true; + + // Отправка изменений + return $account->update() > 0 ? true : false; } return false; @@ -258,7 +332,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface */ public function validatePassword(string $pswd): bool { - return Yii::$app->security->validatePassword($pswd, $this->pswd); + // return yii::$app->security->validatePassword($pswd, $this->pswd); + return $pswd === $this->pswd; } /** @@ -546,11 +621,118 @@ class Account extends Document implements IdentityInterface, PartnerInterface */ public function statusLabel(): string { - return match($this->type) { + return match ($this->type) { 'administrator', 'admin' => 'Администратор', 'moderator', 'moder' => 'Модератор', 'supplier' => 'Поставщик', default => 'Покупатель', }; } + + public function sendMailVerify(): bool + { + if (isset($this->mail, $this->vrfy) && !$this->validateVrfy($this->vrfy)) { + // Данные инициализированы, а регистрация ещё не была подтверждена + + // Отправка письма + yii::$app->mail_system->compose() + ->setFrom(yii::$app->params['mail']['system']) + ->setTo($this->mail) + ->setSubject('Подтверждение регистрации') + ->setHtmlBody(yii::$app->controller->renderPartial('/mails/verify', ['vrfy' => $this->vrfy, 'password' => $this->pswd])) + ->send(); + } + + return false; + } + + /** + * Гениальный генератор пароля + * + * 35 * 19 * 9 = 5985 комбинаций + * + * @return string|null + */ + public static function passwordGenerate(): ?string + { + return match (rand(1, 35)) { + 1 => 'салазки', + 2 => 'запчасти', + 3 => 'инструменты', + 4 => 'детали', + 5 => 'компоненты', + 6 => 'ремни', + 7 => 'шестерни', + 8 => 'блоки', + 9 => 'коронки', + 10 => 'вал', + 11 => 'пыльник', + 12 => 'шкив', + 13 => 'станок', + 14 => 'сальник', + 15 => 'кольцо', + 16 => 'цепь', + 17 => 'редуктор', + 18 => 'фильтр', + 19 => 'клапан', + 20 => 'фару', + 21 => 'мотор', + 22 => 'подшипник', + 23 => 'болт', + 24 => 'стартер', + 25 => 'двигатель', + 26 => 'трубку', + 27 => 'прокладку', + 28 => 'помпу', + 29 => 'запчасть', + 30 => 'втулку', + 31 => 'уплотнение', + 32 => 'ролик', + 33 => 'датчик', + 34 => 'насос', + default => 'машину' + } + . ' ' . match (rand(1, 15)) { + 1 => 'забыли', + 2 => 'отжали', + 3 => 'забрали', + 4 => 'порвали', + 5 => 'украли', + 6 => 'обменяли', + 7 => 'угнали', + 8 => 'взорвали', + 9 => 'поломали', + 1 => 'доломали', + 11 => 'утопили', + 12 => 'испортили', + 13 => 'добили', + 14 => 'разбили', + default => 'сломали' + } + . ' ' . match (rand(1, 9)) { + 1 => 'закажу', + 2 => 'найду', + 3 => 'отыщу', + 4 => 'запрошу', + 5 => 'поищу', + 6 => 'оформлю', + 7 => 'заменю', + 8 => 'поменяю', + default => 'куплю' + } + . ' в скиллпартсе'; + } + + /** + * Пересоздать ключ подтверждения регистрации и пароль к аккаунту + * + * @return bool + */ + public function verifyRegenerate(): bool + { + $this->pswd = static::passwordGenerate(); + $this->vrfy = yii::$app->security->generateRandomString(); + + return $this->update() > 0 ? true : false; + } } diff --git a/mirzaev/skillparts/system/models/AccountForm.php b/mirzaev/skillparts/system/models/AccountForm.php index 634ac4a..7360abd 100644 --- a/mirzaev/skillparts/system/models/AccountForm.php +++ b/mirzaev/skillparts/system/models/AccountForm.php @@ -37,37 +37,45 @@ class AccountForm extends Model [ [ 'mail', - 'pswd' ], 'required', 'message' => 'Заполните поле' ], - // Обязательные поля для регистрации + // Обязательные поля для аутентификации [ [ - 'rept', - 'pols' + 'pswd', ], 'required', 'message' => 'Заполните поле', - 'on' => self::SCENARIO_REGISTRATION + 'on' => self::SCENARIO_AUTHENTICATION ], + // Обязательные поля для регистрации + // [ + // [ + // 'rept', + // 'pols' + // ], + // 'required', + // 'message' => 'Заполните поле', + // 'on' => self::SCENARIO_REGISTRATION + // ], // Повтор пароля - [ - 'rept', - 'compare', - 'compareAttribute' => 'pswd', - 'message' => "Пароли не совпадают", - 'on' => self::SCENARIO_REGISTRATION, - ], + // [ + // 'rept', + // 'compare', + // 'compareAttribute' => 'pswd', + // 'message' => "Пароли не совпадают", + // 'on' => self::SCENARIO_REGISTRATION, + // ], // Принятие политики конфидециальности - [ - 'pols', - 'compare', - 'compareValue' => 'on', - 'message' => "Чтобы продолжить примите нашу политику конфидециальности", - 'on' => self::SCENARIO_REGISTRATION, - ], + // [ + // 'pols', + // 'compare', + // 'compareValue' => 'on', + // 'message' => "Чтобы продолжить примите нашу политику конфидециальности", + // 'on' => self::SCENARIO_REGISTRATION, + // ], // Функция "Запомнить меня" [ 'auto', @@ -180,15 +188,26 @@ class AccountForm extends Model // Запись параметров $this->account->mail = $this->mail; - $this->account->pswd = yii::$app->security->generatePasswordHash($this->pswd); + // $this->account->pswd = yii::$app->security->generatePasswordHash(Account::passwordGenerate()); + $this->account->pswd = $this->pswd = Account::passwordGenerate(); + + if (($account = Account::findByMail($this->mail)) || isset($account) && $account->vrfy !== true) { + // Аккаунт найден, но не подтверждён + + // Удаление аккаунта (сейчас его создадим снова) + $account->delete(); + } // Регистрация if ($this->account->save()) { - // Успешно завершена регистрация + // Успешно завершена регистрация или обновлены данные не до конца зарегистрировавшегося пользователя // Генерация индекса Account::generateIndexes([$this->account]); + // Отправка письма для подтверждения почты + $this->account->sendMailVerify(); + return true; } } diff --git a/mirzaev/skillparts/system/models/Dellin.php b/mirzaev/skillparts/system/models/Dellin.php index c4ebcce..8bb26d1 100644 --- a/mirzaev/skillparts/system/models/Dellin.php +++ b/mirzaev/skillparts/system/models/Dellin.php @@ -46,10 +46,10 @@ class Dellin extends Document /** * Поиск по идентификатору терминала * - * @param string $id Идентификатор терминала + * @param int $id Идентификатор терминала * @param bool $terminal_data_only Запрос только данных терминала */ - public static function searchByTerminalId(string $id, bool $terminal_data_only = false): bool|static|array|null|ArangoDBDocument + public static function searchByTerminalId(int $id, bool $terminal_data_only = false): bool|static|array|null|ArangoDBDocument { if ($terminal_data_only) { return static::find()->foreach(['terminal' => self::collectionName() . '.data["terminals"]["terminal"]'])->where(['terminal["id"] == "' . $id . '"'])->select('terminal')->createCommand()->execute()->getAll()[0]; diff --git a/mirzaev/skillparts/system/models/Order.php b/mirzaev/skillparts/system/models/Order.php index 95184f1..87702a0 100644 --- a/mirzaev/skillparts/system/models/Order.php +++ b/mirzaev/skillparts/system/models/Order.php @@ -465,22 +465,58 @@ class Order extends Document implements DocumentInterface // Доставка автоматическая try { - // Инициализация доставки Dellin (автоматическая) - $connection['delivery'] = Dellin::calcDeliveryAdvanced( - $connection['account']['opts']['delivery_from_terminal'], - yii::$app->user->identity->opts['delivery_to_terminal'], - (int) ($connection['product']['wght'] ?? 0), - (int) ($connection['product']['dmns']['x'] ?? 0), - (int) ($connection['product']['dmns']['y'] ?? 0), - (int) ($connection['product']['dmns']['z'] ?? 0), - count($connection['order_edge_supply']) - ); + // Инициализация данных геолокации + try { + $from = (int) $connection['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; + } + + if ($buffer_connection = $connection['product']['bffr']["$from-$to"] ?? false) { + // Найдены данные доставки в буфере + + if (time() < $buffer_connection['expires']) { + // Срок хранения не превышен, информация актуальна + + // Запись в буфер вывода + $connection['delivery'] = $buffer_connection['data']; + } + } else { + // Инициализация инстанции продукта в базе данных + $product = Product::searchByCatn($connection['product']['catn']); + + // Инициализация доставки Dellin (автоматическая) + $product->bffr = ($product->bffr ?? []) + [ + "$from-$to" => [ + 'data' => $connection['delivery'] = Dellin::calcDeliveryAdvanced( + $from, + $to, + (int) ($connection['product']['wght'] ?? 0), + (int) ($connection['product']['dmns']['x'] ?? 0), + (int) ($connection['product']['dmns']['y'] ?? 0), + (int) ($connection['product']['dmns']['z'] ?? 0), + count($connection['order_edge_supply']) + ), + 'expires' => time() + 86400 + ] + ]; + + // Отправка в базу данных + $product->update(); + } } catch (Exception $e) { $connection['delivery']['error'] = true; - var_dump($e->getMessage()); - var_dump($e->getTrace()); - var_dump($e->getFile()); die; + // var_dump($e->getMessage()); + // var_dump($e->getTrace()); + // var_dump($e->getFile()); + // die; // var_dump(json_decode($e->getMessage(), true)['errors']); die; } @@ -491,23 +527,59 @@ class Order extends Document implements DocumentInterface // Доставка самолётом try { - // Инициализация доставки Dellin (самолётом) - $connection['delivery'] = Dellin::calcDeliveryAdvanced( - $connection['account']['opts']['delivery_from_terminal'], - yii::$app->user->identity->opts['delivery_to_terminal'], - (int) ($connection['product']['wght'] ?? 0), - (int) ($connection['product']['dmns']['x'] ?? 0), - (int) ($connection['product']['dmns']['y'] ?? 0), - (int) ($connection['product']['dmns']['z'] ?? 0), - count($connection['order_edge_supply']), - avia: true - ); + // Инициализация данных геолокации + try { + $from = (int) $connection['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; + } + + if ($buffer_connection = $connection['product']['bffr']["$from-$to-avia"] ?? false) { + // Найдены данные доставки в буфере + + if (time() < $buffer_connection['expires']) { + // Срок хранения не превышен, информация актуальна + + // Запись в буфер вывода + $connection['delivery'] = $buffer_connection['data']; + } + } else { + // Инициализация инстанции продукта в базе данных + $product = Product::searchByCatn($connection['product']['catn']); + + // Инициализация доставки Dellin (автоматическая) + $product->bffr = ($product->bffr ?? []) + [ + "$from-$to-avia" => [ + 'data' => $connection['delivery'] = Dellin::calcDeliveryAdvanced( + $from, + $to, + (int) ($connection['product']['wght'] ?? 0), + (int) ($connection['product']['dmns']['x'] ?? 0), + (int) ($connection['product']['dmns']['y'] ?? 0), + (int) ($connection['product']['dmns']['z'] ?? 0), + count($connection['order_edge_supply']), + avia: true + ), + 'expires' => time() + 86400 + ] + ]; + + // Отправка в базу данных + $product->update(); + } } catch (Exception $e) { $connection['delivery']['error'] = true; - var_dump($e->getMessage()); - var_dump($e->getTrace()); - var_dump($e->getFile()); die; + // var_dump($e->getMessage()); + // var_dump($e->getTrace()); + // var_dump($e->getFile()); + // die; // var_dump(json_decode($e->getMessage(), true)['errors']); die; } diff --git a/mirzaev/skillparts/system/models/Product.php b/mirzaev/skillparts/system/models/Product.php index 62edebb..e4739ce 100644 --- a/mirzaev/skillparts/system/models/Product.php +++ b/mirzaev/skillparts/system/models/Product.php @@ -82,7 +82,8 @@ class Product extends Document 'dmns', 'wght', 'imgs', - 'time' + 'time', + 'bffr', ] ); } @@ -103,6 +104,7 @@ class Product extends Document 'wght' => 'Вес (wght)', 'imgs' => 'Изображения (imgs)', 'time' => 'Срок доставки (time)', + 'bffr' => 'Буфер', 'file_excel' => 'Документ (file_excel)', 'file_image' => 'Изображение (file_image)', 'group' => 'Группа (group)' diff --git a/mirzaev/skillparts/system/models/Settings.php b/mirzaev/skillparts/system/models/Settings.php index 9465304..61842a4 100644 --- a/mirzaev/skillparts/system/models/Settings.php +++ b/mirzaev/skillparts/system/models/Settings.php @@ -26,7 +26,8 @@ class Settings extends Document parent::attributes(), [ 'search_period', - 'search_connect_keep' + 'search_connect_keep', + 'delivery_from_default' ] ); } @@ -40,7 +41,8 @@ class Settings extends Document parent::attributeLabels(), [ 'search_period' => 'Поисковый период', - 'search_connect_keep' => 'Режим удержания' + 'search_connect_keep' => 'Режим удержания', + 'delivery_from_default' => 'Место отправки поставки по умолчанию' ] ); } @@ -55,7 +57,8 @@ class Settings extends Document [ [ [ - 'search_period' + 'search_period', + 'delivery_from_default' ], 'integer', 'message' => '{attribute} должен хранить цифровое значение' @@ -70,4 +73,14 @@ class Settings extends Document ] ); } + + /** + * Найти активную запись с настройками + * + * @todo Доделать + */ + public static function search(): ?self + { + return static::findOne(['active' => true]); + } } diff --git a/mirzaev/skillparts/system/models/connection/Dellin.php b/mirzaev/skillparts/system/models/connection/Dellin.php index 3cf3460..335ac11 100644 --- a/mirzaev/skillparts/system/models/connection/Dellin.php +++ b/mirzaev/skillparts/system/models/connection/Dellin.php @@ -8,7 +8,8 @@ use yii; use yii\base\Model; use app\models\Dellin as DellinModel; - +use app\models\Product; +use app\models\Settings; use GuzzleHttp\Client as Guzzle; use GuzzleHttp\Exception\ClientException as GuzzleException; @@ -54,15 +55,14 @@ class Dellin extends Model // ]) // }); // } - /** * Рассчет доставки (расширенный) * * Рассчет нескольких товаров идет через простое перемножение результатов доставки одного товара * В API всегда идет рассчет для одного товара, так было решено * - * @param string $from Идентификатор терминала Dellin - * @param string $to Идентификатор терминала Dellin + * @param int $from Идентификатор терминала Dellin + * @param int $to Идентификатор терминала Dellin * @param int $weight Вес (г) * @param int $x Ширина (cм) * @param int $y Высота (cм) @@ -74,8 +74,9 @@ class Dellin extends Model * @todo Загружать помимо терминалов ещё и адреса, чтобы доделать доставку малогабаритных грузов * Разрабраться с параметрами 0,54м * 0,39м * 0,39м (0.082134м) и 0.1 куб метр в чем разница */ - public static function calcDeliveryAdvanced(string $from, string $to, int $weight, int $x, int $y, int $z, int $amount = 1, bool $avia = false): array + public static function calcDeliveryAdvanced(int $from, int $to, int $weight, int $x, int $y, int $z, int $amount = 1, bool $avia = false): array { + return self::handle(function () use ($from, $to, $weight, $x, $y, $z, $amount, $avia) { // Всё готово к работе @@ -85,7 +86,7 @@ class Dellin extends Model // Значения по умолчанию, если указан 0 $x === 0 and $x = 25; - $y === 0 and $y = 55; + $y === 0 and $y = 50; $z === 0 and $z = 25; $weight === 0 and $weight = 300; diff --git a/mirzaev/skillparts/system/views/account/index.php b/mirzaev/skillparts/system/views/account/index.php index d81ecf7..1aa2743 100644 --- a/mirzaev/skillparts/system/views/account/index.php +++ b/mirzaev/skillparts/system/views/account/index.php @@ -72,12 +72,18 @@ use app\models\AccountForm; diff --git a/mirzaev/skillparts/system/views/account/verify.php b/mirzaev/skillparts/system/views/account/verify.php new file mode 100644 index 0000000..e45f856 --- /dev/null +++ b/mirzaev/skillparts/system/views/account/verify.php @@ -0,0 +1,11 @@ +
+
+
+

Подтвердите аккаунт

+

Мы выслали вам письмо с паролем и ссылкой на активацию

+ + Повторить +
+
+
+ diff --git a/mirzaev/skillparts/system/views/cart/index.php b/mirzaev/skillparts/system/views/cart/index.php index fa07984..ec2fe0c 100644 --- a/mirzaev/skillparts/system/views/cart/index.php +++ b/mirzaev/skillparts/system/views/cart/index.php @@ -95,7 +95,7 @@ use DateTime;
-

$delivery_icon $delivery дн

+

$delivery_icon $delivery дн

$cost @@ -134,7 +134,7 @@ use DateTime; 0 руб

- + Оформить заказ
@@ -143,3 +143,12 @@ use DateTime; + diff --git a/mirzaev/skillparts/system/views/error.php b/mirzaev/skillparts/system/views/error.php index 19cce35..2edde94 100644 --- a/mirzaev/skillparts/system/views/error.php +++ b/mirzaev/skillparts/system/views/error.php @@ -2,16 +2,16 @@ use yii\helpers\Html; -$this->title = $name; +$this->title = $title; ?>
-

title) ?>

+

-
- +
+
-
\ No newline at end of file +
diff --git a/mirzaev/skillparts/system/views/mails/verify.php b/mirzaev/skillparts/system/views/mails/verify.php new file mode 100644 index 0000000..7e1cc56 --- /dev/null +++ b/mirzaev/skillparts/system/views/mails/verify.php @@ -0,0 +1,17 @@ +
+
+ + SkillParts + +
+
+

Подтвердите регистрацию

+

Ваш пароль: ""

+ Нажимая на кнопку ниже вы соглашаетесь с политикой конфиденциальности + Принять и подтвердить +
+
+ Вы получили это сообщение потому, что на ваш почтовый адрес была совершена регистрация + Если это были не вы, проверьте безопасность ваших аккаунтов и свяжитесь с администрацией +
+
diff --git a/mirzaev/skillparts/system/views/policy/index.php b/mirzaev/skillparts/system/views/policy/index.php index f77aad1..d1bf8ea 100644 --- a/mirzaev/skillparts/system/views/policy/index.php +++ b/mirzaev/skillparts/system/views/policy/index.php @@ -4,7 +4,7 @@
-

Политика конфидециальности

+

Политика конфиденциальности

В данной Политике обработки персональных данных (далее – Политика) мы информируем Вас о порядке обработки Ваших персональных данных при использовании нашего веб-сайта https://skillparts.ru/ (далее - «Сайт») в целях исполнения заключенных договоров.

diff --git a/mirzaev/skillparts/system/web/css/main.css b/mirzaev/skillparts/system/web/css/main.css index 5b4458e..6329702 100644 --- a/mirzaev/skillparts/system/web/css/main.css +++ b/mirzaev/skillparts/system/web/css/main.css @@ -13,12 +13,12 @@ } a { - color: #0b3397; + color: #123EAB; cursor: pointer; } a:hover { - color: #001752; + color: #09246a; } p { diff --git a/mirzaev/skillparts/system/web/js/account.js b/mirzaev/skillparts/system/web/js/account.js index 8122ca4..07cb8c3 100644 --- a/mirzaev/skillparts/system/web/js/account.js +++ b/mirzaev/skillparts/system/web/js/account.js @@ -142,86 +142,96 @@ function registration_start(form, type) { // Удаление кнопок аутентификации form.children[4].remove(); - // Инициализация поля ввода пароля - let password = form.children[3]; + // Инициализация поля ввода почты + let mail = form.children[2]; - // Изменение стиля - password.classList.add('mb-2'); + // // Инициализация поля ввода пароля + // let password = form.children[3]; // Инициализация кнопки регистрации let registration = form.children[4]; - // Изменение размера + // Изменение отступа поля + mail.classList.add('mb-4'); + + // // Изменение стиля + // password.classList.add('mb-2'); + + // Удаление поля ввода пароля + form.children[3].remove(); + + // Изменение размера кнопки регистрации registration.classList.remove('btn-sm'); registration.classList.add('btn'); + // Изменение вызываемой функции registration.setAttribute('onclick', 'return registration_end(this.parentElement);'); - // Инициализация оболочки поля повтора пароля - let password_repeat_wrap = document.createElement('div'); - password_repeat_wrap.setAttribute('class', 'mb-3 field-accountform-rept required'); + // // Инициализация оболочки поля повтора пароля + // let password_repeat_wrap = document.createElement('div'); + // password_repeat_wrap.setAttribute('class', 'mb-3 field-accountform-rept required'); - // Инициализация поля повтора пароля - let password_repeat = document.createElement('input'); - password_repeat.setAttribute('id', 'accountform-rept' + postfix); - password_repeat.classList.add('form-control'); - password_repeat.setAttribute('type', 'password'); - password_repeat.setAttribute('name', 'AccountForm[rept]'); - password_repeat.setAttribute('placeholder', 'Повтор пароля'); - password_repeat.setAttribute('aria-required', 'true'); - password_repeat.setAttribute('aria-invalid', 'true'); + // // Инициализация поля повтора пароля + // let password_repeat = document.createElement('input'); + // password_repeat.setAttribute('id', 'accountform-rept' + postfix); + // password_repeat.classList.add('form-control'); + // password_repeat.setAttribute('type', 'password'); + // password_repeat.setAttribute('name', 'AccountForm[rept]'); + // password_repeat.setAttribute('placeholder', 'Повтор пароля'); + // password_repeat.setAttribute('aria-required', 'true'); + // password_repeat.setAttribute('aria-invalid', 'true'); - // Инициализация элемента с текстом ошибок поля повтора пароля - let password_repeat_errors = document.createElement('p'); - password_repeat_errors.setAttribute('class', 'help-block help-block-error small'); + // // Инициализация элемента с текстом ошибок поля повтора пароля + // let password_repeat_errors = document.createElement('p'); + // password_repeat_errors.setAttribute('class', 'help-block help-block-error small'); - // Инициализация оболочки элемента с подтверждением политики персональных данных - let private_policy_wrap = document.createElement('div'); - private_policy_wrap.setAttribute('class', 'mb-3 field-accountform-pols required'); + // // Инициализация оболочки элемента с подтверждением политики персональных данных + // let private_policy_wrap = document.createElement('div'); + // private_policy_wrap.setAttribute('class', 'mb-3 field-accountform-pols required'); - // Инициализация оболочки для класса checkbox для элемента с подтверждением политики персональных данных - let private_policy_checkbox = document.createElement('div'); - private_policy_checkbox.classList.add('checkbox'); - private_policy_checkbox.classList.add('text-center'); + // // Инициализация оболочки для класса checkbox для элемента с подтверждением политики персональных данных + // let private_policy_checkbox = document.createElement('div'); + // private_policy_checkbox.classList.add('checkbox'); + // private_policy_checkbox.classList.add('text-center'); - // Инициализация оболочки для класса label для элемента с подтверждением политики персональных данных - let private_policy_label = document.createElement('label'); - private_policy_label.setAttribute('for', 'accountform-pols'); + // // Инициализация оболочки для класса label для элемента с подтверждением политики персональных данных + // let private_policy_label = document.createElement('label'); + // private_policy_label.setAttribute('for', 'accountform-pols'); - // Инициализация скрытого элемента с подтверждением политики персональных данных - let private_policy_hidden = document.createElement('input'); - private_policy_hidden.setAttribute('type', 'hidden'); - private_policy_hidden.setAttribute('name', 'AccountForm[pols]'); - private_policy_hidden.setAttribute('value', '0'); + // // Инициализация скрытого элемента с подтверждением политики персональных данных + // let private_policy_hidden = document.createElement('input'); + // private_policy_hidden.setAttribute('type', 'hidden'); + // private_policy_hidden.setAttribute('name', 'AccountForm[pols]'); + // private_policy_hidden.setAttribute('value', '0'); - // Инициализация элемента с подтверждением политики персональных данных - let private_policy = document.createElement('input'); - private_policy.setAttribute('id', 'accountform-pols' + postfix); - private_policy.setAttribute('type', 'checkbox'); - private_policy.setAttribute('name', 'AccountForm[pols]'); - private_policy.setAttribute('placeholder', 'Политика конфиденциальности'); + // // Инициализация элемента с подтверждением политики персональных данных + // let private_policy = document.createElement('input'); + // private_policy.setAttribute('id', 'accountform-pols' + postfix); + // private_policy.setAttribute('type', 'checkbox'); + // private_policy.setAttribute('name', 'AccountForm[pols]'); + // private_policy.setAttribute('placeholder', 'Политика конфиденциальности'); - // Инициализация оболочки элемента с текстом элемента с подтверждением политики персональных данных - let private_policy_text_wrap = document.createElement('small'); - private_policy_text_wrap.setAttribute('class', 'ml-2'); + // // Инициализация оболочки элемента с текстом элемента с подтверждением политики персональных данных + // let private_policy_text_wrap = document.createElement('small'); + // private_policy_text_wrap.setAttribute('class', 'ml-2'); - // Инициализация элемента с текстом элемента с подтверждением политики персональных данных - let private_policy_text = document.createElement('a'); - private_policy_text.setAttribute('href', '/policy'); - private_policy_text.innerText = 'Политика'; + // // Инициализация элемента с текстом элемента с подтверждением политики персональных данных + // let private_policy_text = document.createElement('a'); + // private_policy_text.setAttribute('href', '/policy'); + // private_policy_text.innerText = 'Политика'; - // Запись в документ - form.insertBefore(password_repeat_wrap, registration); - password_repeat_wrap.appendChild(password_repeat); - password_repeat_wrap.appendChild(password_repeat_errors); - form.insertBefore(private_policy_wrap, registration); - private_policy_wrap.appendChild(private_policy_checkbox); - private_policy_checkbox.appendChild(private_policy_label); - private_policy_label.appendChild(private_policy); - private_policy_label.appendChild(private_policy_text_wrap); - private_policy_text_wrap.appendChild(private_policy_text); - private_policy_text_wrap.appendChild(document.createTextNode(' конфиденциальности')); + // // Запись в документ + // form.insertBefore(password_repeat_wrap, registration); + // password_repeat_wrap.appendChild(password_repeat); + // password_repeat_wrap.appendChild(password_repeat_errors); + // form.insertBefore(private_policy_wrap, registration); + // private_policy_wrap.appendChild(private_policy_checkbox); + // private_policy_checkbox.appendChild(private_policy_label); + // private_policy_label.appendChild(private_policy); + // private_policy_label.appendChild(private_policy_text_wrap); + // private_policy_text_wrap.appendChild(private_policy_text); + // private_policy_text_wrap.appendChild(document.createTextNode(' конфиденциальности')); // Инициализация заголовка let title = form.children[1]; diff --git a/mirzaev/skillparts/system/web/js/cart.js b/mirzaev/skillparts/system/web/js/cart.js index a320c5e..718d733 100644 --- a/mirzaev/skillparts/system/web/js/cart.js +++ b/mirzaev/skillparts/system/web/js/cart.js @@ -42,9 +42,9 @@ function cart_delete() { /** * Сформировать заказ */ -function cart_pay() { +function cart_request() { $.ajax({ - url: '/order/pay', + url: '/order/request', type: 'post', dataType: 'json', data: { @@ -298,8 +298,6 @@ function cart_list_comment_save(catn, element) { return true; } -cart_cost_calculate(); - function cart_response(data, status) { // Обработка ответов diff --git a/mirzaev/skillparts/system/web/js/menu.js b/mirzaev/skillparts/system/web/js/menu.js index f2ef941..0cff50b 100644 --- a/mirzaev/skillparts/system/web/js/menu.js +++ b/mirzaev/skillparts/system/web/js/menu.js @@ -141,4 +141,4 @@ function menu_error(data, status) { // Обновление документа $('meta[name=csrf-token]').prop("content", data._csrf); } -} \ No newline at end of file +} diff --git a/mirzaev/skillparts/system/web/js/reinitialization.js b/mirzaev/skillparts/system/web/js/reinitialization.js index 6958434..f88f328 100644 --- a/mirzaev/skillparts/system/web/js/reinitialization.js +++ b/mirzaev/skillparts/system/web/js/reinitialization.js @@ -2,8 +2,8 @@ function reinitialization(target) { // Уничтожает все внешние файлы загруженные внутри блока с переданным ID и загружает их в и if (target !== undefined) { - links = target.getElementsByTagName('link'); - scripts = target.getElementsByTagName('script'); + let links = target.getElementsByTagName('link'); + let scripts = target.getElementsByTagName('script'); // Запись CSS for (link of links) { @@ -16,14 +16,19 @@ function reinitialization(target) { // Запись JS for (script of scripts) { - new_script = document.createElement("script"); - new_script.src = script.src; - document.getElementsByTagName("body")[0].appendChild(new_script); - script.remove(); + if (script.getAttribute('src') !== null) { + new_script = document.createElement("script"); + new_script.src = script.src; + document.getElementsByTagName("body")[0].appendChild(new_script); + } else { + eval(script.innerText); + } + + // script.remove(); По каким-то причинам не работает, надо бы доделать } return true; } return false; -}; \ No newline at end of file +}; diff --git a/mirzaev/skillparts/system/web/js/verify.js b/mirzaev/skillparts/system/web/js/verify.js new file mode 100644 index 0000000..a182181 --- /dev/null +++ b/mirzaev/skillparts/system/web/js/verify.js @@ -0,0 +1,59 @@ +/** + * Записать в корзину (создать корзину, если не существует) + */ + function verify_resend() { + // Инициализация + + $.ajax({ + url: '/verify/send', + type: 'post', + dataType: 'json', + success: verify_response_success, + error: verify_response_error + }); + + return false; +} + + +function verify_response(data, status) { + // Обработка ответов + + // Основной блок + if (data.main !== undefined) { + main = document.getElementsByTagName('main')[0]; + + // Обновление документа + main.innerHTML = data.main; + + // Реинициализация + reinitialization(main); + } + + // Перенаправление + if (data.redirect !== undefined) { + // Перенаправление + history.pushState({}, document.title, data.redirect); + } + + // CSRF-токен + if (data._csrf !== undefined) { + // Обновление документа + $('meta[name=csrf-token]').prop("content", data._csrf); + } +} + +function verify_response_success(data, status) { + // Обработка ответов от удавшихся запросов + + verify_response(data, status); +} + +function verify_response_error(data, status) { + // Обработка ответов от неудавшихся запросов + + // Инициализвация + data = data.responseJSON; + + verify_response(data, status); +}