Compare commits
125 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
761db76970 | ||
![]() |
9ebb4385cb | ||
1918db868d | |||
ec2d478a62 | |||
![]() |
bfb0fbe4de | ||
![]() |
f57d7f6460 | ||
![]() |
2488594d81 | ||
![]() |
ebf21a0642 | ||
![]() |
c41e5d4c1c | ||
![]() |
b3f2ef72da | ||
![]() |
d21c6ce0ed | ||
![]() |
a11a21d1ab | ||
![]() |
c11cac01aa | ||
![]() |
fc38285d06 | ||
![]() |
4fafe85639 | ||
![]() |
3ec9b920d2 | ||
![]() |
33cc3efc7b | ||
![]() |
2c2e830f0a | ||
![]() |
e1fa1c1f5a | ||
![]() |
79773503eb | ||
![]() |
68d26118ab | ||
![]() |
ab97036822 | ||
![]() |
4f80a465bc | ||
![]() |
e8454defc2 | ||
![]() |
bcc60fcd41 | ||
![]() |
675c6cbc39 | ||
![]() |
16ce1ccf59 | ||
![]() |
4da846825b | ||
![]() |
82d6747425 | ||
![]() |
a31214ad6a | ||
![]() |
c5e24d7838 | ||
![]() |
856d4f1995 | ||
![]() |
2f4f38b2aa | ||
![]() |
4683cf51db | ||
![]() |
e941fbd782 | ||
![]() |
24f079f622 | ||
![]() |
4c91c3192a | ||
![]() |
a971aef392 | ||
![]() |
ab3c3b8b05 | ||
![]() |
8fda1af398 | ||
![]() |
953f99245c | ||
![]() |
8abdaf4626 | ||
![]() |
bc98da67ce | ||
![]() |
70d2ae2935 | ||
![]() |
913a67d400 | ||
![]() |
b08f7f7d0a | ||
![]() |
7a4f12aa94 | ||
![]() |
9fb2e4dc90 | ||
![]() |
3f0b342d48 | ||
![]() |
6daa3112b6 | ||
![]() |
8f32df518f | ||
![]() |
29ea6a9a79 | ||
![]() |
1a1392d950 | ||
![]() |
1d06acf2e0 | ||
![]() |
e6944c9b6b | ||
![]() |
2b194457f5 | ||
![]() |
95b5d920b9 | ||
![]() |
53281ee422 | ||
![]() |
a235ed25ac | ||
![]() |
6bab42ac33 | ||
![]() |
f76f15b503 | ||
![]() |
dbd2982115 | ||
![]() |
2a90f36f0c | ||
![]() |
43272d051e | ||
![]() |
7e63c8be97 | ||
![]() |
8c53872955 | ||
![]() |
134ce8f162 | ||
![]() |
a36f62e510 | ||
![]() |
3cb2aa1a15 | ||
![]() |
d9337944b1 | ||
![]() |
fab247b4bc | ||
![]() |
96d9a0307c | ||
![]() |
42d20fd313 | ||
![]() |
57710d8a96 | ||
![]() |
044d80ef3e | ||
![]() |
dc30b8c6bb | ||
![]() |
5e01240938 | ||
![]() |
b7c271141f | ||
![]() |
d087256181 | ||
![]() |
8270eaed11 | ||
![]() |
7647ca69d5 | ||
![]() |
b80b887a11 | ||
![]() |
49ed9bf59e | ||
![]() |
a6d8ee5e59 | ||
![]() |
b2b5b0737c | ||
![]() |
5f3623a258 | ||
![]() |
177de6b3ef | ||
![]() |
b3b5111006 | ||
![]() |
bf821a9819 | ||
![]() |
2ca929e122 | ||
![]() |
26686374f0 | ||
![]() |
d5bb9137f7 | ||
![]() |
fc59eff6db | ||
![]() |
83295650c9 | ||
![]() |
c4035d8ad3 | ||
![]() |
cc1e7e7d66 | ||
![]() |
8c4ca42d3c | ||
![]() |
cb315a9fcf | ||
![]() |
9e120afc24 | ||
![]() |
44ca2e9f92 | ||
![]() |
0fa1c977ca | ||
![]() |
0d654ad790 | ||
![]() |
4f898bf796 | ||
![]() |
21cec0c5b4 | ||
![]() |
fab290eacd | ||
![]() |
4f8a99d8e2 | ||
![]() |
248f5470f9 | ||
![]() |
7e5f39c8e9 | ||
![]() |
5d9228ec1b | ||
![]() |
3cf9f24a20 | ||
![]() |
03b52d071d | ||
![]() |
3ea7a9e1eb | ||
![]() |
85e3d5f1bd | ||
![]() |
0a8c74a9a3 | ||
![]() |
ada91bd0b7 | ||
![]() |
7865cac5d4 | ||
![]() |
d4bc3e2263 | ||
![]() |
fe0453f91b | ||
![]() |
a11a5da2e1 | ||
![]() |
b44194d5a3 | ||
![]() |
874d5c3f55 | ||
![]() |
b50049eb67 | ||
![]() |
c169347687 | ||
![]() |
912b25bea2 | ||
![]() |
7ca19da826 |
@@ -13,8 +13,9 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0.0",
|
||||
"twbs/bootstrap": ">=4.5",
|
||||
"php": "^8.1.0",
|
||||
"ext-intl": "~8.0",
|
||||
"twbs/bootstrap": "4.6.0",
|
||||
"yiisoft/yii2": "2.*",
|
||||
"yiisoft/yii2-bootstrap": ">=2.0.0",
|
||||
"yiisoft/yii2-swiftmailer": ">=2.0.0",
|
||||
@@ -26,20 +27,15 @@
|
||||
"carono/yii2-1c-exchange": "^0.3.1",
|
||||
"yiisoft/yii2-imagine": "*",
|
||||
"mirzaev/yii2-arangodb": ">=2.1.x-dev",
|
||||
"mirzaev/yii2-arangodb-sessions": ">=1.1.x-dev"
|
||||
"mirzaev/yii2-arangodb-sessions": ">=1.1.x-dev",
|
||||
"guzzlehttp/guzzle": "^7.3",
|
||||
"phpoffice/phpspreadsheet": "^1.18",
|
||||
"hflabs/dadata": "^20.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": ">=4.1",
|
||||
"codeception/module-webdriver": ">=1.0.0",
|
||||
"yiisoft/yii2-debug": ">=2.1.0",
|
||||
"yiisoft/yii2-gii": ">=2.1.0",
|
||||
"yiisoft/yii2-faker": ">=2.0.0",
|
||||
"codeception/verify": ">=1.1.0",
|
||||
"codeception/specify": ">=0.4.6",
|
||||
"symfony/browser-kit": ">=2.7",
|
||||
"codeception/module-filesystem": ">=1.0.0",
|
||||
"codeception/module-yii2": ">=1.0.0",
|
||||
"codeception/module-asserts": ">=1.0.0"
|
||||
"yiisoft/yii2-faker": ">=2.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
5433
composer.lock
generated
5433
composer.lock
generated
File diff suppressed because it is too large
Load Diff
4
mirzaev/skillparts/system/assets/.gitignore
vendored
4
mirzaev/skillparts/system/assets/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/import
|
||||
/import/*
|
||||
/invoices/*
|
||||
/accounts/*
|
||||
|
@@ -1,22 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||
* @license http://www.yiiframework.com/license/
|
||||
*/
|
||||
|
||||
namespace app\assets;
|
||||
|
||||
use yii;
|
||||
use yii\web\AssetBundle;
|
||||
use yii\web\View;
|
||||
|
||||
/**
|
||||
* Main application asset bundle.
|
||||
*
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class AppAsset extends AssetBundle
|
||||
{
|
||||
public $basePath = '@webroot';
|
||||
@@ -37,17 +25,24 @@ class AppAsset extends AssetBundle
|
||||
'js/bootstrap/bootstrap.min.js',
|
||||
'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.1/?apikey=ff21ed7c-2d34-4f91-8d7f-2144ec3e4397&lang=ru_RU',
|
||||
'js/moment.min.js',
|
||||
'js/menu.js',
|
||||
'js/main.js',
|
||||
'js/account.js',
|
||||
'js/search.js',
|
||||
'js/notification.js',
|
||||
'js/reinitialization.js'
|
||||
'js/reinitialization.js',
|
||||
'js/yandex/metrika.js',
|
||||
'js/yandex/geolocation.js',
|
||||
'https://www.googletagmanager.com/gtag/js?id=G-6XYKBJJWR4',
|
||||
'js/google/analytics.js'
|
||||
];
|
||||
public $jsOptions = [
|
||||
// 'position' => View::POS_HEAD
|
||||
];
|
||||
public $depends = [
|
||||
'yii\web\YiiAsset',
|
||||
'yii\web\YiiAsset'
|
||||
// 'yii\bootstrap\BootstrapAsset'
|
||||
];
|
||||
}
|
||||
|
36
mirzaev/skillparts/system/commands/AccountController.php
Normal file
36
mirzaev/skillparts/system/commands/AccountController.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
|
||||
use app\models\Account;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
/**
|
||||
* Сгенерировать уникальные идентификаторы
|
||||
*
|
||||
* @param int|string $_key Ключ аккаунта (оставить пустым или отправить "all", если для всех)
|
||||
* @param bool $init Параметр обозначающий изменение только для тех у кого ранее идентификатор задан не был (без перезаписи)
|
||||
*/
|
||||
public function actionGenerateIndex(int|string $_key = null, bool $init = true)
|
||||
{
|
||||
// Инициализация
|
||||
$accounts = empty($_key) || strcasecmp($_key, 'all') === 0 ? Account::readAll() : [Account::searchById($_key)];
|
||||
|
||||
// Генерация
|
||||
$amount = Account::generateIndexes($accounts, $init);
|
||||
|
||||
echo 'Обработано аккаунтов: ' . $amount;
|
||||
|
||||
if ($amount > 0) {
|
||||
// Был успешно обработан минимум 1 аккаунт
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
}
|
35
mirzaev/skillparts/system/commands/DellinController.php
Normal file
35
mirzaev/skillparts/system/commands/DellinController.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
|
||||
use app\models\connection\Dellin;
|
||||
|
||||
class DellinController extends Controller
|
||||
{
|
||||
/**
|
||||
* Импортировать города из ДеловыеЛинии
|
||||
*/
|
||||
public function actionImportCities()
|
||||
{
|
||||
if (Dellin::importCities()) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Импортировать терминалы из ДеловыеЛинии
|
||||
*/
|
||||
public function actionImportTerminals(?int $account = null)
|
||||
{
|
||||
if (Dellin::importTerminals($account)) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
}
|
63
mirzaev/skillparts/system/commands/ImportController.php
Normal file
63
mirzaev/skillparts/system/commands/ImportController.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
|
||||
use moonland\phpexcel\Excel;
|
||||
|
||||
use app\models\Supply;
|
||||
use app\models\File;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Импортировать города из ДеловыеЛинии
|
||||
*/
|
||||
public function actionCities()
|
||||
{
|
||||
if (Dellin::importCities()) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Импортировать терминалы из ДеловыеЛинии
|
||||
*/
|
||||
public function actionTerminals()
|
||||
{
|
||||
if (Dellin::importTerminals()) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Импортировать поставки из файлов
|
||||
*/
|
||||
public function actionSupplies(int $amount = 3)
|
||||
{
|
||||
try {
|
||||
$files = File::searchSuppliesNeededToLoad($amount);
|
||||
|
||||
if ($files > 0) {
|
||||
// Найдены файлы
|
||||
|
||||
foreach ($files as $file) {
|
||||
// Перебор файлов для загрузки
|
||||
|
||||
// Загрузка в базу данных
|
||||
Supply::loadExcel($file);
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
}
|
24
mirzaev/skillparts/system/commands/SuppliesController.php
Normal file
24
mirzaev/skillparts/system/commands/SuppliesController.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
|
||||
use app\models\Supply;
|
||||
use app\models\File;
|
||||
|
||||
class SuppliesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Импортировать города из ДеловыеЛинии
|
||||
*/
|
||||
public function actionImport(int $amount = 3)
|
||||
{
|
||||
if (Dellin::importCities($amount)) {
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
return ExitCode::UNSPECIFIED_ERROR;
|
||||
}
|
||||
}
|
136
mirzaev/skillparts/system/commands/TestController.php
Normal file
136
mirzaev/skillparts/system/commands/TestController.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace app\commands;
|
||||
|
||||
use yii\console\Controller;
|
||||
use yii\console\ExitCode;
|
||||
|
||||
use app\models\Invoice;
|
||||
use app\models\Product;
|
||||
use app\models\ProductGroup;
|
||||
use app\models\ImportEdgeSupply;
|
||||
|
||||
class TestController extends Controller
|
||||
{
|
||||
public function actionInvoice($buyer = 123123, $order = 0)
|
||||
{
|
||||
// Генерация счета
|
||||
Invoice::generate($order, $this->renderPartial('/invoice/order/pattern', [
|
||||
'buyer' => [
|
||||
'id' => $buyer,
|
||||
'info' => 'Неизвестно'
|
||||
],
|
||||
'order' => [
|
||||
'id' => $order,
|
||||
'date' => time(),
|
||||
'entries' => [
|
||||
[
|
||||
'title' => 'Тестовое вхождение',
|
||||
'amount' => [
|
||||
'value' => 1,
|
||||
'unit' => 'шт'
|
||||
],
|
||||
'cost' => [
|
||||
'value' => 1000,
|
||||
'unit' => 'руб'
|
||||
],
|
||||
'type' => 'supply'
|
||||
],
|
||||
[
|
||||
'title' => 'Тестовое вхождение',
|
||||
'amount' => [
|
||||
'value' => 1,
|
||||
'unit' => 'шт'
|
||||
],
|
||||
'cost' => [
|
||||
'value' => 1000,
|
||||
'unit' => 'руб'
|
||||
],
|
||||
'type' => 'supply'
|
||||
],
|
||||
[
|
||||
'title' => 'Тестовое вхождение',
|
||||
'amount' => [
|
||||
'value' => 1,
|
||||
'unit' => 'шт'
|
||||
],
|
||||
'cost' => [
|
||||
'value' => 1000,
|
||||
'unit' => 'руб'
|
||||
],
|
||||
'type' => 'supply'
|
||||
],
|
||||
[
|
||||
'title' => 'Тестовое вхождение',
|
||||
'amount' => [
|
||||
'value' => 5,
|
||||
'unit' => 'шт'
|
||||
],
|
||||
'cost' => [
|
||||
'value' => 1000,
|
||||
'unit' => 'руб'
|
||||
],
|
||||
'type' => 'supply'
|
||||
]
|
||||
]
|
||||
]
|
||||
]));
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
|
||||
public function actionAnalogs($_id = 'product/51987159')
|
||||
{
|
||||
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
public function actionWriteAnalog($_id = 'product/51987159', $analog = 'product/12051485')
|
||||
{
|
||||
// Инициализация товара
|
||||
$product = Product::searchById($_id);
|
||||
|
||||
// Инициализация аналога
|
||||
$analog = Product::searchById($analog);
|
||||
|
||||
if (!$group = ProductGroup::searchByProduct($product)) {
|
||||
// Не найдена группа товаров
|
||||
|
||||
// Запись новой группы
|
||||
$group = ProductGroup::writeEmpty(active: true);
|
||||
|
||||
// Запись товара в группу
|
||||
$group->writeProduct($product);
|
||||
}
|
||||
|
||||
if ($_group = ProductGroup::searchByProduct($analog)) {
|
||||
// Найдена друга группа у товара который надо добавить в группу
|
||||
|
||||
// Перенос всех участников (включая целевой товар)
|
||||
return $group->transfer($_group);
|
||||
} else {
|
||||
// Не найдена группа у товара который надо добавить в группу
|
||||
|
||||
// Запись целевого товара в группу
|
||||
return $group->writeProduct($analog);
|
||||
}
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
public function actionReadAnalog($_id = 'product/51987159')
|
||||
{
|
||||
var_dump((ProductGroup::searchByProduct(Product::searchById($_id))->searchProducts()));
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
|
||||
public function actionEdgeMax()
|
||||
{
|
||||
var_dump(ImportEdgeSupply::generateVersion());
|
||||
|
||||
return ExitCode::OK;
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
$config = [
|
||||
'id' => 'skillparts-console',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'bootstrap' => ['log'],
|
||||
@@ -34,4 +34,15 @@ return [
|
||||
'class' => 'yii\faker\FixtureController',
|
||||
],
|
||||
]
|
||||
];
|
||||
];
|
||||
|
||||
|
||||
if (YII_ENV_DEV) {
|
||||
// configuration adjustments for 'dev' environment
|
||||
$config['bootstrap'][] = 'gii';
|
||||
$config['modules']['gii'] = [
|
||||
'class' => 'yii\gii\Module',
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
|
@@ -1,7 +1,28 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'adminEmail' => 'admin@example.com',
|
||||
'senderEmail' => 'noreply@example.com',
|
||||
'senderName' => 'Example.com mailer',
|
||||
'captcha' => [
|
||||
'suppliers' => [
|
||||
'public' => null,
|
||||
'secret' => null
|
||||
]
|
||||
],
|
||||
'mail' => [
|
||||
'system' => null,
|
||||
'info' => null
|
||||
],
|
||||
'dellin' => [
|
||||
'nickname' => null,
|
||||
'password' => null,
|
||||
'key' => null
|
||||
],
|
||||
'cdek' => [
|
||||
'nickname' => null,
|
||||
'password' => null,
|
||||
'key' => null
|
||||
],
|
||||
'dadata' => [
|
||||
'key' => null,
|
||||
'secret' => null
|
||||
]
|
||||
];
|
||||
|
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Application configuration shared by all test types
|
||||
*/
|
||||
return [
|
||||
'id' => 'skillparts-tests',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
'@npm' => '@vendor/npm-asset',
|
||||
],
|
||||
'language' => 'en-US',
|
||||
'components' => [
|
||||
'db' => require __DIR__ . '/test_db.php',
|
||||
'mailer' => [
|
||||
'useFileTransport' => true,
|
||||
],
|
||||
'assetManager' => [
|
||||
'basePath' => __DIR__ . '/../web/assets',
|
||||
],
|
||||
'urlManager' => [
|
||||
'showScriptName' => true,
|
||||
],
|
||||
'user' => [
|
||||
'identityClass' => 'app\models\Account',
|
||||
],
|
||||
'request' => [
|
||||
'cookieValidationKey' => 'test',
|
||||
'enableCsrfValidation' => false,
|
||||
// but if you absolutely need it set cookie domain to localhost
|
||||
/*
|
||||
'csrfCookie' => [
|
||||
'domain' => 'localhost',
|
||||
],
|
||||
*/
|
||||
],
|
||||
],
|
||||
'params' => require __DIR__ . '/params.php',
|
||||
];
|
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$db = require __DIR__ . '/db.php';
|
||||
// test database! Important not to run tests on production or development databases
|
||||
$db['dsn'] = 'mysql:host=localhost;dbname=yii2basic_test';
|
||||
|
||||
return $db;
|
@@ -4,6 +4,7 @@ $config = [
|
||||
'id' => 'skillparts',
|
||||
'basePath' => dirname(__DIR__),
|
||||
'bootstrap' => ['log'],
|
||||
'defaultRoute' => 'main',
|
||||
'aliases' => [
|
||||
'@vendor' => dirname(__DIR__) . '/../../../vendor',
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
@@ -50,12 +51,30 @@ $config = [
|
||||
'errorHandler' => [
|
||||
'errorAction' => 'error',
|
||||
],
|
||||
'mailer' => [
|
||||
'mail_info' => [
|
||||
'class' => 'yii\swiftmailer\Mailer',
|
||||
// send all mails to a file by default. You have to set
|
||||
// 'useFileTransport' to false and configure a transport
|
||||
// for the mailer to send real emails.
|
||||
'useFileTransport' => true,
|
||||
'useFileTransport' => false,
|
||||
'transport' => [
|
||||
'class' => 'Swift_SmtpTransport',
|
||||
'host' => 'smtp.yandex.com',
|
||||
'username' => 'info@skillparts.ru',
|
||||
'password' => 'SkillParts_1337',
|
||||
'port' => '465',
|
||||
'encryption' => 'ssl',
|
||||
|
||||
],
|
||||
],
|
||||
'mail_system' => [
|
||||
'class' => 'yii\swiftmailer\Mailer',
|
||||
'useFileTransport' => false,
|
||||
'transport' => [
|
||||
'class' => 'Swift_SmtpTransport',
|
||||
'host' => 'smtp.yandex.com',
|
||||
'username' => 'system@skillparts.ru',
|
||||
'password' => 'System01001010Null',
|
||||
'port' => '465',
|
||||
'encryption' => 'ssl',
|
||||
],
|
||||
],
|
||||
'log' => [
|
||||
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||
@@ -75,9 +94,28 @@ $config = [
|
||||
'class' => 'yii\rest\UrlRule',
|
||||
'controller' => 'main'
|
||||
],
|
||||
'product/<catn:[^/]+>' => 'product/index',
|
||||
'product/<catn:[^/]+>/<action:(write|edit|delete)>/<target:(title|catn|desc|image)>' => 'product/<action>-<target>',
|
||||
'orders' => 'order/index'
|
||||
'<_key:[0-9]+>' => 'account/index',
|
||||
'<_key:[0-9]+>/<target:[^/]+>/<action:(read|edit|delete|regenerate)>' => 'account/<action>',
|
||||
'<_key:[0-9]+>/files/<file:[^/]+>' => 'account/file',
|
||||
'<_key:[0-9]+>/<action:(accept|decline|data)>' => 'account/<action>',
|
||||
'product/<prod:[^/]+>/<catn:[^/]+>' => 'product/index',
|
||||
'product/<prod:[^/]+>/<catn:[^/]+>/<action:(write|delete|connect|disconnect|status)>' => 'product/<action>',
|
||||
'<section:(product|cart)>/<prod:[^/]+>/<catn:[^/]+>/<action:(read|write|edit|delete)>/<target:(title|catn|name|dscr|prod|dmns|wght|image|cover|comm)>' => '<section>/<action>-<target>',
|
||||
'products/<action:(read)>/<stts:[^/]+>/' => 'product/<action>',
|
||||
'profile/geolocation/<action:(init|write)>' => 'profile/geolocation-<action>',
|
||||
'profile/panel/<panel:(suppliers)>/<block:(requests)>/<action:(search)>' => 'profile/panel-<panel>-<block>-<action>',
|
||||
'profile/imports/<action:(delete)>' => 'profile/imports-<action>',
|
||||
'profile/warehouses/<action:(write|delete|rename|close|open)>' => 'profile/warehouses-<action>',
|
||||
'orders' => 'order/index',
|
||||
'orders/<filter:[^/]+>' => 'order/index',
|
||||
'orders/<catn:[^/]+>/<action:(accept)>' => 'order/<action>',
|
||||
'orders/supply/<catn:[^/]+>/<action:(read|write|edit|delete)>' => 'order/supply-<action>',
|
||||
'orders/supply/<catn:[^/]+>/<action:(read|write|edit|delete)>/<target:(stts|cost|time|comm)>' => 'order/supply-<action>-<target>',
|
||||
'invoices/<order:[^/]+>' => 'invoice/index',
|
||||
'invoices/<order:[^/]+>/<action:(download)>' => 'invoice/<action>',
|
||||
'verify/send' => 'verify/send',
|
||||
'verify/<vrfy:[^/]+>' => 'verify/index',
|
||||
'terminals/<action:(read|write)>' => 'terminal/<action>'
|
||||
],
|
||||
],
|
||||
|
||||
@@ -85,11 +123,13 @@ $config = [
|
||||
'modules' => [
|
||||
'exchange' => [
|
||||
'class' => 'carono\exchange1c\ExchangeModule',
|
||||
'exchangeDocuments' => true,
|
||||
'validateModelOnSave' => true,
|
||||
'groupClass' => 'app\models\SupplyGroup',
|
||||
'productClass' => 'app\models\Supply',
|
||||
'offerClass' => 'app\models\SupplyEdgeProduct',
|
||||
'partnerClass' => 'app\models\Account',
|
||||
'documentClass' => 'app\models\Purchase',
|
||||
'documentClass' => 'app\models\Order',
|
||||
'auth' => function ($mail, $pswd) {
|
||||
// Необходимо уничтожить AccountForm
|
||||
// return (new \app\models\AccountForm())->authentication($mail, $pswd);
|
||||
@@ -104,6 +144,120 @@ $config = [
|
||||
]
|
||||
],
|
||||
'params' => require __DIR__ . '/params.php',
|
||||
'on beforeAction' => function ($event) {
|
||||
if (yii::$app->user->isGuest) {
|
||||
// Гость
|
||||
} else {
|
||||
// Пользователь
|
||||
|
||||
// Подтверждение почты
|
||||
if (yii::$app->user->identity->vrfy !== true) {
|
||||
// Почта не подтверждена
|
||||
|
||||
if (!(str_starts_with(yii::$app->request->getPathInfo(), 'verify')
|
||||
|| match (yii::$app->request->getPathInfo()) {
|
||||
'policy', 'notification', 'identification' => true,
|
||||
default => false
|
||||
})) {
|
||||
// Фильтрация страниц
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
yii::$app->response->statusCode = 401;
|
||||
|
||||
return [
|
||||
'main' => yii::$app->controller->renderPartial('/account/verify'),
|
||||
'redirect' => '/registration',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
} else {
|
||||
// Подразумевается как GET-запрос
|
||||
|
||||
// Переадресация на страницу указывающую на необходимость подтвердить почту
|
||||
yii::$app->response->redirect('/verify')->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Согласие с офертой
|
||||
if (
|
||||
!(isset(yii::$app->session['offer_buyer_accepted'])
|
||||
&& yii::$app->session['offer_buyer_accepted'] === true)
|
||||
&& (!isset(yii::$app->user->identity->acpt['buyer'])
|
||||
|| yii::$app->user->identity->acpt['buyer'] === false)
|
||||
) {
|
||||
// Нет подтверждения офферты пользователя
|
||||
|
||||
if (!(str_starts_with(yii::$app->request->getPathInfo(), 'verify')
|
||||
|| str_starts_with(yii::$app->request->getPathInfo(), 'offer')
|
||||
|| match (yii::$app->request->getPathInfo()) {
|
||||
'notification', 'identification' => true,
|
||||
default => false
|
||||
})) {
|
||||
// Фильтрация страниц
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
yii::$app->response->statusCode = 401;
|
||||
|
||||
return [
|
||||
'main' => yii::$app->controller->renderPartial('/offer/index'),
|
||||
'redirect' => '/registration',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
} else {
|
||||
// Подразумевается как GET-запрос
|
||||
|
||||
// Переадресация на оферту
|
||||
yii::$app->response->redirect('/offer')->send();
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
(isset(yii::$app->user->identity->agnt)
|
||||
&& yii::$app->user->identity->agnt === true)
|
||||
&& !(isset(yii::$app->session['offer_supplier_accepted'])
|
||||
&& yii::$app->session['offer_supplier_accepted'] === true)
|
||||
&& (!isset(yii::$app->user->identity->acpt['supplier'])
|
||||
|| yii::$app->user->identity->acpt['supplier'] === false)
|
||||
) {
|
||||
// Нет подтверждения офферты поставщика
|
||||
|
||||
if (!(str_starts_with(yii::$app->request->getPathInfo(), 'verify')
|
||||
|| str_starts_with(yii::$app->request->getPathInfo(), 'offer')
|
||||
|| match (yii::$app->request->getPathInfo()) {
|
||||
'notification', 'identification' => true,
|
||||
default => false
|
||||
})) {
|
||||
// Фильтрация страниц
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
yii::$app->response->statusCode = 401;
|
||||
|
||||
return [
|
||||
'main' => $this->renderPartial('/offer/supplier'),
|
||||
'redirect' => '/registration',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
} else {
|
||||
// Подразумевается как GET-запрос
|
||||
|
||||
// Переадресация на оферту
|
||||
yii::$app->response->redirect('/offer/suppliers')->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (YII_ENV_DEV) {
|
||||
@@ -118,4 +272,4 @@ if (YII_ENV_DEV) {
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
return $config;
|
||||
|
484
mirzaev/skillparts/system/controllers/AccountController.php
Normal file
484
mirzaev/skillparts/system/controllers/AccountController.php
Normal file
@@ -0,0 +1,484 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
use yii\web\Cookie;
|
||||
use yii\filters\AccessControl;
|
||||
|
||||
use app\models\Account;
|
||||
use app\models\AccountForm;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'rules' => [
|
||||
[
|
||||
'allow' => true,
|
||||
'actions' => [
|
||||
'file',
|
||||
'data',
|
||||
'restore',
|
||||
'generate-password'
|
||||
]
|
||||
],
|
||||
[
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
'actions' => [
|
||||
'index',
|
||||
'edit',
|
||||
'regenerate'
|
||||
]
|
||||
],
|
||||
[
|
||||
'allow' => true,
|
||||
'actions' => [
|
||||
'read',
|
||||
'accept',
|
||||
'decline'],
|
||||
'matchCallback' => function ($rule, $action): bool {
|
||||
if (
|
||||
!yii::$app->user->isGuest
|
||||
&& (yii::$app->user->identity->type === 'administrator'
|
||||
|| yii::$app->user->identity->type === 'moderator')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
],
|
||||
[
|
||||
'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' => yii::$app->request->pathInfo,
|
||||
'_csrf' => Yii::$app->request->getCsrfToken()
|
||||
]);
|
||||
} else if (Yii::$app->request->isGet) {
|
||||
// GET-запрос
|
||||
|
||||
$this->redirect('/authentication');
|
||||
}
|
||||
}
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
return $this->renderPartial('/accounts/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Подтверждение
|
||||
*
|
||||
* @param int $_key Идентификатор аккаунта
|
||||
*/
|
||||
public function actionAccept(string $_key)
|
||||
{
|
||||
if (yii::$app->user->isGuest) {
|
||||
// Аккаунт не аутентифицирован
|
||||
} else {
|
||||
if (yii::$app->request->isPost) {
|
||||
// AJAX-POST-запрос
|
||||
|
||||
if (
|
||||
yii::$app->user->identity->type === 'administrator'
|
||||
|| yii::$app->user->identity->type === 'moderator'
|
||||
) {
|
||||
// Запрос произведен уполномоченным
|
||||
|
||||
if ($account = Account::searchById(Account::collectionName() . '/' . $_key)) {
|
||||
// Аккаунт найден
|
||||
|
||||
$account->type = 'user';
|
||||
|
||||
if ($account->update() > 0) {
|
||||
// Удалось перезаписать данные в хранилище
|
||||
|
||||
// Запись в журнал
|
||||
$account->journal('accepted into suppliers');
|
||||
|
||||
// Отправка письма с подтверждением аккаунта
|
||||
$account->sendMailVerify();
|
||||
|
||||
// Настройка формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Запись кода ответа
|
||||
yii::$app->response->statusCode = 500;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отказ
|
||||
*
|
||||
* @param int $_key Идентификатор аккаунта
|
||||
*/
|
||||
public function actionDecline(string $_key)
|
||||
{
|
||||
if (yii::$app->user->isGuest) {
|
||||
// Аккаунт не аутентифицирован
|
||||
} else {
|
||||
if (yii::$app->request->isPost) {
|
||||
// AJAX-POST-запрос
|
||||
|
||||
if (
|
||||
yii::$app->user->identity->type === 'administrator'
|
||||
|| yii::$app->user->identity->type === 'moderator'
|
||||
) {
|
||||
// Запрос произведен уполномоченным
|
||||
|
||||
if ($account = Account::searchById(Account::collectionName() . '/' . $_key)) {
|
||||
// Аккаунт найден
|
||||
|
||||
// Отправка письма с отказом и причиной
|
||||
$account->sendMailDecline(yii::$app->request->post('reason') ?? yii::$app->request->get('reason'));
|
||||
|
||||
// Настройка формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
// Удаление аккаунта
|
||||
return $account->delete() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Запись кода ответа
|
||||
yii::$app->response->statusCode = 500;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запрос файла
|
||||
*
|
||||
* @param int $_key Идентификатор аккаунта
|
||||
* @param string $file Путь до файла от каталога аккаунта
|
||||
*/
|
||||
public function actionFile(string $_key, string $file)
|
||||
{
|
||||
// Инициализация файла
|
||||
$file = YII_PATH_PUBLIC . "/../assets/accounts/$_key/files/$file";
|
||||
|
||||
if (file_exists($file)) {
|
||||
// Удалось найти файл
|
||||
|
||||
return $this->response->sendFile($file);
|
||||
} else {
|
||||
// Не удалось найти файл
|
||||
|
||||
// Запись кода ответа
|
||||
yii::$app->response->statusCode = 500;
|
||||
|
||||
// Перенаправление на страницу аккаунта
|
||||
return yii::$app->response->redirect("/$_key");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование параметра
|
||||
*
|
||||
* @param int $_key
|
||||
* @param string $target
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function actionEdit(int $_key, string $target)
|
||||
{
|
||||
if (yii::$app->user->isGuest) {
|
||||
// Аккаунт не аутентифицирован
|
||||
} else {
|
||||
if (yii::$app->request->isPost) {
|
||||
// AJAX-POST-запрос
|
||||
|
||||
if (
|
||||
$_key === yii::$app->user->identity->_key ||
|
||||
(yii::$app->user->identity->type === 'administrator'
|
||||
|| yii::$app->user->identity->type === 'moderator')
|
||||
) {
|
||||
// Запрос произведен с изменяемого аккаунта, либо уполномоченным
|
||||
|
||||
if ($account = Account::searchById(Account::collectionName() . '/' . $_key)) {
|
||||
// Аккаунт найден
|
||||
|
||||
// Запись параметра
|
||||
$account->{$target} = yii::$app->request->post('value') ?? yii::$app->request->get('value') ?? 'Неизвестно';
|
||||
|
||||
// Настройка формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return $account->update() > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Запись кода ответа
|
||||
yii::$app->response->statusCode = 500;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Регенерация параметра
|
||||
*
|
||||
* @param int $_key Идентификатор
|
||||
* @param string $target
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function actionRegenerate(int $_key, string $target)
|
||||
{
|
||||
if (yii::$app->user->isGuest) {
|
||||
// Аккаунт не аутентифицирован
|
||||
} else {
|
||||
if (yii::$app->request->isPost) {
|
||||
// AJAX-POST-запрос
|
||||
|
||||
if ($account = Account::searchById(Account::collectionName() . '/' . $_key)) {
|
||||
// Аккаунт найден
|
||||
|
||||
if (
|
||||
$account->_key === yii::$app->user->identity->_key ||
|
||||
(yii::$app->user->identity->type === 'administrator'
|
||||
|| yii::$app->user->identity->type === 'moderator')
|
||||
) {
|
||||
// Запрос произведен с изменяемого аккаунта, либо уполномоченным
|
||||
|
||||
if ($target === 'indx') {
|
||||
// Запрошена регенерация индекса
|
||||
|
||||
// Настройка формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
// Регенерация индекса
|
||||
return Account::generateIndexes([$account], force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Запись кода ответа
|
||||
yii::$app->response->statusCode = 500;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Информация
|
||||
*
|
||||
* @param int $_key Идентификатор
|
||||
*/
|
||||
public function actionData(int $_key)
|
||||
{
|
||||
if (yii::$app->request->isPost) {
|
||||
// AJAX-POST-запрос
|
||||
|
||||
// Инициализация аккаунта (тот кто выполняет запрос)
|
||||
$account = account::initAccount();
|
||||
|
||||
// Поиск данных об аккаунта (запрашиваемом)
|
||||
$data = account::initAccount($_key)->getAttributes();
|
||||
|
||||
if ($account->isAdmin() || $account->isModer()) {
|
||||
// Авторизован как работник
|
||||
} else if ((int) $account->_key === $_key) {
|
||||
// Авторизован как владелец аккаунта
|
||||
|
||||
unset(
|
||||
$data['vrfy'],
|
||||
$data['geol'],
|
||||
$data['auth'],
|
||||
$data['jrnl'],
|
||||
$data['acpt'],
|
||||
$data['pswd']
|
||||
);
|
||||
} else {
|
||||
// Не авторизован
|
||||
|
||||
// Очистка от защищенных свойств
|
||||
$data = [
|
||||
'_key' => $data['_key'],
|
||||
'indx' => $data['indx'],
|
||||
'agnt' => $data['agnt'],
|
||||
'type' => $data['type']
|
||||
];
|
||||
}
|
||||
|
||||
// Настройка формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return [
|
||||
'data' => $data,
|
||||
'_csrf' => Yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Восстановление пароля
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionRestore()
|
||||
{
|
||||
// Инициализация
|
||||
$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');
|
||||
|
||||
// Фильтрация
|
||||
$target = match ($target) {
|
||||
'panel' => 'panel',
|
||||
'main' => 'main',
|
||||
default => 'main'
|
||||
};
|
||||
|
||||
// Рендер для всплывающей панели
|
||||
$panel = $target === 'panel';
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// AJAX-POST-запрос
|
||||
|
||||
// Настройка кода ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
if (yii::$app->user->isGuest || $model->validate()) {
|
||||
// Аккаунт не аутентифицирован и проверка пройдена
|
||||
|
||||
// Отправка запроса на генерацию пароля и запись ответа
|
||||
$return = [
|
||||
'status' => Account::restoreSend($model->mail),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
|
||||
return $return;
|
||||
} else {
|
||||
// Аккаунт аутентифицирован
|
||||
|
||||
// Настройка кода ответа
|
||||
yii::$app->response->statusCode = 400;
|
||||
|
||||
return [
|
||||
'redirect' => '/',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация нового пароля
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionGeneratePassword(string $id, string $key)
|
||||
{
|
||||
if ($account = Account::searchById(Account::collectionName() . "/$id")) {
|
||||
// Найден аккаунт
|
||||
|
||||
if ($account->chpk === $key) {
|
||||
// Ключи совпадают
|
||||
|
||||
// Инициализация буфера пароля
|
||||
$old = $account->pswd;
|
||||
|
||||
// Генерация пароля
|
||||
$account->restoreGenerate();
|
||||
|
||||
if ($account->pswd !== $old) {
|
||||
// Успешно сгенерирован новый пароль
|
||||
|
||||
// Инициализация формы аутентификации
|
||||
$form = new AccountForm;
|
||||
|
||||
// Запись параметров
|
||||
$form->mail = $account->mail;
|
||||
$form->pswd = $account->pswd;
|
||||
|
||||
// Аутентификация
|
||||
$form->authentication();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Перенаправление на главную страницу
|
||||
$this->redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация нового пароля
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionRead(int $page = 1): string|array|null
|
||||
{
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
// Инициализация входных параметров
|
||||
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 20;
|
||||
$order = yii::$app->request->post('order') ?? yii::$app->request->get('order') ?? ['DESC'];
|
||||
|
||||
// Инициализация cookie
|
||||
$cookies = yii::$app->response->cookies;
|
||||
|
||||
// Чтение аккаунтов
|
||||
$accounts = Account::read(limit: $amount, page: $page, order: $order);
|
||||
|
||||
// Запись формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return [
|
||||
'accounts' => $this->renderPartial('/account/list', compact('accounts', 'amount', 'page')),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -3,7 +3,8 @@
|
||||
namespace app\controllers;
|
||||
|
||||
use app\models\AccountForm;
|
||||
|
||||
use app\models\Order;
|
||||
use app\models\Notification;
|
||||
use yii;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
@@ -11,6 +12,7 @@ use yii\filters\AccessControl;
|
||||
|
||||
use Throwable;
|
||||
use Exception;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
|
||||
class AuthenticationController extends Controller
|
||||
{
|
||||
@@ -35,32 +37,45 @@ class AuthenticationController extends Controller
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
if (yii::$app->request->isAjax) {
|
||||
// Инициализация
|
||||
$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) {
|
||||
'panel' => 'panel',
|
||||
'main' => 'main',
|
||||
default => 'main'
|
||||
};
|
||||
|
||||
// Рендер для всплывающей панели
|
||||
$panel = $target === 'panel';
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// AJAX-POST-запрос
|
||||
|
||||
// Инициализация
|
||||
$model = new AccountForm(yii::$app->request->post('AccountForm'));
|
||||
$model->scenario = $model::SCENARIO_AUTHENTICATION;
|
||||
|
||||
// Настройка кода ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
if (!yii::$app->user->isGuest || $model->authentication()) {
|
||||
// Аккаунт аутентифицирован
|
||||
|
||||
// Создание сессии
|
||||
// yii::$app->session->open();
|
||||
// Отправка уведомления
|
||||
Notification::_write('Вы аутентифицированы с устройства ' . $_SERVER['HTTP_USER_AGENT'] . ' ' . $_SERVER['REMOTE_ADDR'], true, yii::$app->user->identity->_key, Notification::TYPE_NOTICE);
|
||||
|
||||
// Инициализация
|
||||
$notifications_button = $this->renderPartial('/notification/button');
|
||||
$notifications_panel_full = true;
|
||||
$notifications_panel = $this->renderPartial('/notification/panel', compact('notifications_panel_full'));
|
||||
$notifications_panel = $this->renderPartial('/notification/panel', ['notifications_panel_full' => true]);
|
||||
$cart_button = $this->renderPartial('/cart/button', ['cart_amount' => Order::count(supplies: true)]);
|
||||
|
||||
// Запись ответа
|
||||
$return = [
|
||||
'menu' => $this->renderPartial('/account/panel/authenticated', compact(
|
||||
'notifications_button',
|
||||
'notifications_panel',
|
||||
'notifications_panel_full'
|
||||
'cart_button'
|
||||
)),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
@@ -71,21 +86,13 @@ class AuthenticationController extends Controller
|
||||
// Запись ответа
|
||||
$return['redirect'] = '/' . $cookies['redirect'];
|
||||
|
||||
try {
|
||||
if (empty($return['main'] = $this->renderPartial($return['redirect']))) {
|
||||
throw new Exception('Представление найдено, но вернуло пустой результат');
|
||||
}
|
||||
} catch (Throwable $t) {
|
||||
$return['main'] = $this->renderPartial($return['redirect'] . '/index');
|
||||
}
|
||||
|
||||
// Генерация и запись
|
||||
// $controller = 'app\\controllers\\' . ucfirst($cookies['redirect']) . 'Controller';
|
||||
// $action = 'action' . ucfirst($cookies['redirect_action']);
|
||||
// $return['main'] = (new $controller())->$action();
|
||||
|
||||
// Очистка cookie
|
||||
unset(yii::$app->response->cookies['redirect']);
|
||||
|
||||
yii::$app->response->format = Response::FORMAT_HTML;
|
||||
|
||||
// Переадресация
|
||||
return $this->redirect($return['redirect']);
|
||||
} else {
|
||||
// Не найдено cookie с переадресацией
|
||||
|
||||
@@ -102,10 +109,11 @@ class AuthenticationController extends Controller
|
||||
} else {
|
||||
// Аккаунт не аутентифицирован
|
||||
|
||||
// Настройка кода ответа
|
||||
yii::$app->response->statusCode = 400;
|
||||
|
||||
return [
|
||||
'main' => $this->renderPartial('/account/index', compact('model')),
|
||||
$target => $this->renderPartial('/account/index', compact('model', 'panel')),
|
||||
'redirect' => '/authentication',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
@@ -115,7 +123,7 @@ class AuthenticationController extends Controller
|
||||
if (!yii::$app->user->isGuest) {
|
||||
yii::$app->response->redirect('/');
|
||||
} else {
|
||||
return $this->render('/account/index');
|
||||
return $this->render('/account/index', compact('model', 'panel'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
mirzaev/skillparts/system/controllers/BuyersController.php
Normal file
15
mirzaev/skillparts/system/controllers/BuyersController.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii\web\Controller;
|
||||
|
||||
class BuyersController extends Controller
|
||||
{
|
||||
public function actionIndex()
|
||||
{
|
||||
return $this->renderPartial('/buyers/index');
|
||||
}
|
||||
}
|
@@ -4,44 +4,133 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use app\models\Account;
|
||||
use yii;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
use yii\web\Cookie;
|
||||
|
||||
use app\models\Product;
|
||||
use app\models\Order;
|
||||
use app\models\OrderEdgeSupply;
|
||||
use app\models\Notification;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CartController extends Controller
|
||||
{
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'rules' => [
|
||||
[
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
'actions' => [
|
||||
'index',
|
||||
'edit-comm',
|
||||
'count'
|
||||
]
|
||||
],
|
||||
[
|
||||
'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');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Страница: "Корзина"
|
||||
*
|
||||
* @see $this->behaviors Доступ только аутентифицированным
|
||||
*/
|
||||
public function actionIndex(): string|array|null
|
||||
public function actionIndex(): string|array|null|Response
|
||||
{
|
||||
// Инициализация
|
||||
$page = yii::$app->request->get('page') ?? yii::$app->request->post('page') ?? 1;
|
||||
$account = yii::$app->user;
|
||||
$account = Account::initAccount();
|
||||
|
||||
// Поиск корзины (текущего заказа)
|
||||
$model = Order::search();
|
||||
$data = Order::searchSmart()[0] ?? null;
|
||||
|
||||
if (empty($model)) {
|
||||
if (empty($data['order'])) {
|
||||
// Корзина не инициализирована
|
||||
|
||||
// Инициализация
|
||||
$model = new Order();
|
||||
$model->save() or throw new Exception('Не удалось инициализировать заказ');
|
||||
$data['order'] = new Order();
|
||||
|
||||
// Подключение
|
||||
$model->connect($account);
|
||||
if ($data['order']->save()) {
|
||||
// Удалось инициализировать заказ
|
||||
|
||||
// Подключение заказа к аккаунту
|
||||
$data['order']->connect($account);
|
||||
} else {
|
||||
throw new Exception('Не удалось инициализировать заказ');
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация содержимого корзины
|
||||
$supplies = $model->content(10, $page);
|
||||
$data['supplies'] = $data['order']->supplies(10, $page);
|
||||
|
||||
// Инициализация данных списка для выбора терминала
|
||||
$delivery_to_terminal_list = $account->genListTerminalsTo();
|
||||
$array_unshift_in_start = function (array &$array, string|int $key, mixed $value) {
|
||||
$array = array_reverse($array, true);
|
||||
$array[$key] = $value;
|
||||
return $array = array_reverse($array, true);
|
||||
};
|
||||
$array_write_default_value = function (array &$array, string $key = 'Выберите', string $value = 'Выберите') use ($array_unshift_in_start) {
|
||||
if (isset($array[$key])) {
|
||||
// Смещение или ассоциация найдена
|
||||
|
||||
// Деинициализация
|
||||
unset($array[$key]);
|
||||
|
||||
// Инициализация
|
||||
$array_unshift_in_start($array, $key, $value);
|
||||
}
|
||||
};
|
||||
|
||||
// Сортировка по алфавиту
|
||||
asort($delivery_to_terminal_list);
|
||||
|
||||
// Перемещение в начало массива значения "Выберите"
|
||||
$array_write_default_value($delivery_to_terminal_list);
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
@@ -49,13 +138,103 @@ class CartController extends Controller
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return [
|
||||
'main' => $this->renderPartial('index', compact('model', 'supplies')),
|
||||
'main' => $this->renderPartial('index', compact('account', 'data', 'delivery_to_terminal_list')),
|
||||
'title' => 'Корзина',
|
||||
'redirect' => '/cart',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
|
||||
return $this->render('index', compact('model', 'supplies'));
|
||||
return $this->render('index', compact('account', 'data', 'delivery_to_terminal_list'));
|
||||
}
|
||||
|
||||
public function actionEditComm(string $catn, string $prod): array|string|null
|
||||
{
|
||||
// Инициализация
|
||||
$return = [
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
|
||||
if (is_null($catn) || is_null($prod)) {
|
||||
// Не получен артикул
|
||||
|
||||
yii::$app->response->statusCode = 500;
|
||||
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ($edges = OrderEdgeSupply::searchBySupplyCatnAndProd($catn, $prod, Order::searchByType()[0]['order'])) {
|
||||
// Рёбра найдены (связи заказа с поставкой)
|
||||
|
||||
// Инициализация
|
||||
$amount = 0;
|
||||
|
||||
foreach ($edges as $edge) {
|
||||
// Перебор рёбер (связей заказа с поставкой)
|
||||
|
||||
// Инициализация
|
||||
$text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? 'Комментарий к заказу';
|
||||
|
||||
$comm = $edge->comm ?? null;
|
||||
$edge->comm = empty($text) ? 'Комментарий к заказу' : $text;
|
||||
|
||||
if ($edge->save()) {
|
||||
// Ребро обновлено
|
||||
|
||||
// Запись в журнал
|
||||
$edge->journal('update', ['comm' => ['from' => $comm, 'to' => $edge->comm]]);
|
||||
|
||||
// Обновление счётчика
|
||||
++$amount;
|
||||
|
||||
// Запись в буфер ответа
|
||||
$return['comm'] = $edge->comm;
|
||||
}
|
||||
}
|
||||
|
||||
if ($amount > 0) {
|
||||
// Удалось записать минимум 1 связь с поставкой
|
||||
|
||||
Notification::_write("Обновлён комментарий к товару $catn ($amount шт)");
|
||||
} else {
|
||||
// Не удалось записать минимум 1 связь с поставкой
|
||||
|
||||
Notification::_write("Не удалось обновить комментарий к товару $catn");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Конец алгоритма
|
||||
*/
|
||||
end:
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
if ($model = Product::searchByCatnAndProd($catn, $prod)) {
|
||||
return $this->render('index', compact('model'));
|
||||
} else {
|
||||
return $this->redirect('/');
|
||||
}
|
||||
}
|
||||
|
||||
public function actionCount(): array|string|null
|
||||
{
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
// Настройка типа ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return [
|
||||
'button' => $this->renderPartial('/cart/button', ['cart_amount' => Order::count(supplies: true)]),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
$code = $exception->statusCode ?? $exception->getCode() ?? 0;
|
||||
|
||||
// Запись названия ошибки
|
||||
$name = match ($exception->statusCode) {
|
||||
$title = match ($code) {
|
||||
404 => '404 (Не найдено)',
|
||||
default => $exception->getName()
|
||||
default => '500 (Ошибка сервера)'
|
||||
};
|
||||
|
||||
// Запись сообщения об ошибке
|
||||
$message = match ($exception->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'));
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
|
||||
use app\models\AccountForm;
|
||||
use app\models\Order;
|
||||
|
||||
class IdentificationController extends Controller
|
||||
{
|
||||
@@ -38,14 +39,15 @@ class IdentificationController extends Controller
|
||||
|
||||
// Инициализация
|
||||
$notifications_button = $this->renderPartial('/notification/button');
|
||||
$notifications_panel_full = true;
|
||||
$notifications_panel = $this->renderPartial('/notification/panel', compact('notifications_panel_full'));
|
||||
$notifications_panel = $this->renderPartial('/notification/panel', ['notifications_panel_full' => true]);
|
||||
$cart_button = $this->renderPartial('/cart/button', ['cart_amount' => Order::count(supplies: true)]);
|
||||
|
||||
// Запись ответа
|
||||
$return = [
|
||||
'menu' => $this->renderPartial('/account/panel/authenticated', compact(
|
||||
'notifications_button',
|
||||
'notifications_panel'
|
||||
'notifications_panel',
|
||||
'cart_button'
|
||||
)),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
|
46
mirzaev/skillparts/system/controllers/InvoiceController.php
Normal file
46
mirzaev/skillparts/system/controllers/InvoiceController.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii;
|
||||
use yii\web\Controller;
|
||||
|
||||
use app\models\Order;
|
||||
|
||||
class InvoiceController extends Controller
|
||||
{
|
||||
public function actionIndex(int $order)
|
||||
{
|
||||
if ($order = Order::searchById(Order::collectionName() . '/' . $order)) {
|
||||
|
||||
return $this->renderPartial('/invoice/order/pattern', [
|
||||
'account' => yii::$app->user->identity,
|
||||
'order' => [
|
||||
'id' => $order->_key,
|
||||
'date' => $order->date ?? time() // @todo доделать
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function actionDownload(int $order)
|
||||
{
|
||||
// Инициализация файла
|
||||
$file = YII_PATH_PUBLIC . '/../assets/invoices/' . $order . '/invoice.xlsx';
|
||||
|
||||
if (file_exists($file)) {
|
||||
// Удалось найти файл
|
||||
|
||||
return $this->response->sendFile($file);
|
||||
} else {
|
||||
// Не удалось найти файл
|
||||
|
||||
// Запись кода ответа
|
||||
yii::$app->response->statusCode = 500;
|
||||
|
||||
return yii::$app->response->redirect('/orders');
|
||||
}
|
||||
}
|
||||
}
|
@@ -92,7 +92,7 @@ class NotificationController extends Controller
|
||||
* @param bool $new Активация проверки на то, что уведомление не получено
|
||||
* @param bool $count Посчитать
|
||||
*/
|
||||
$search = function (bool $new = false, bool $count = false) use ($model, $account, $type, $let, $limit): array|int {
|
||||
$search = function (bool $new = false, bool $count = false) use ($model, $account, $type, $let, $limit): array|int|null|Notification {
|
||||
if ($count) {
|
||||
// Запрошен подсчёт непрочитанных уведомлений
|
||||
|
||||
@@ -164,7 +164,7 @@ class NotificationController extends Controller
|
||||
goto end;
|
||||
}
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
foreach (is_array($notifications) ? $notifications : [$notifications] as $notification) {
|
||||
// Перебор найденных уведомлений
|
||||
|
||||
if ($preload) {
|
||||
@@ -174,7 +174,7 @@ class NotificationController extends Controller
|
||||
}
|
||||
|
||||
// Запись ребра: ПОЛЬЗОВАТЕЛЬ -> УВЕДОМЛЕНИЕ (о том, что уведомление прочитано)
|
||||
AccountEdgeNotification::write(yii::$app->user->id, $notification->readId(), $type);
|
||||
AccountEdgeNotification::write($account->readId(), $notification->readId(), $type);
|
||||
}
|
||||
|
||||
if (yii::$app->request->post('last')) {
|
||||
@@ -184,7 +184,7 @@ class NotificationController extends Controller
|
||||
$notification = $notifications[0];
|
||||
|
||||
$return['popup'] = [
|
||||
'html' => $this->renderPartial('popup', compact('model', 'notification')),
|
||||
'html' => $this->renderPartial('popup', compact('model', 'notification', 'account')),
|
||||
'id' => 'popup/' . $notification->readId()
|
||||
];
|
||||
} else if (yii::$app->request->post('stream')) {
|
||||
|
115
mirzaev/skillparts/system/controllers/OfferController.php
Normal file
115
mirzaev/skillparts/system/controllers/OfferController.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\web\Cookie;
|
||||
use yii\web\Response;
|
||||
use yii\web\Controller;
|
||||
|
||||
class OfferController extends Controller
|
||||
{
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'rules' => [
|
||||
[
|
||||
'allow' => true,
|
||||
'actions' => ['index', 'suppliers'],
|
||||
],
|
||||
[
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
'actions' => ['accept', 'accept-suppliers']
|
||||
],
|
||||
[
|
||||
'allow' => false,
|
||||
'roles' => ['?'],
|
||||
'denyCallback' => [$this, 'accessDenied']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function accessDenied()
|
||||
{
|
||||
// Инициализация
|
||||
$cookies = yii::$app->response->cookies;
|
||||
|
||||
// Запись cookie с редиректом, который выполнится после авторизации
|
||||
$cookies->add(new Cookie([
|
||||
'name' => 'offer',
|
||||
'value' => yii::$app->request->pathInfo
|
||||
]));
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
// Настройка
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
// Генерация ответа
|
||||
yii::$app->response->content = json_encode([
|
||||
'redirect' => '/authentication',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
]);
|
||||
} else if (Yii::$app->request->isGet) {
|
||||
// GET-запрос
|
||||
|
||||
$this->redirect('/authentication');
|
||||
}
|
||||
}
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
return $this->render('/offer/index');
|
||||
}
|
||||
|
||||
public function actionSuppliers()
|
||||
{
|
||||
return $this->render('/offer/supplier');
|
||||
}
|
||||
|
||||
public function actionAccept()
|
||||
{
|
||||
// Инициализация
|
||||
yii::$app->user->identity->acpt ?? yii::$app->user->identity->acpt = [];
|
||||
|
||||
// Запись
|
||||
yii::$app->user->identity->acpt += ['buyer' => true];
|
||||
|
||||
if (yii::$app->user->identity->save()) {
|
||||
// Удалось записать данные
|
||||
|
||||
// Запись в сессию
|
||||
yii::$app->session['offer_buyer_accepted'] = true;
|
||||
|
||||
|
||||
yii::$app->response->redirect('/');
|
||||
}
|
||||
}
|
||||
|
||||
public function actionAcceptSuppliers()
|
||||
{
|
||||
// Инициализация
|
||||
yii::$app->user->identity->acpt ?? yii::$app->user->identity->acpt = [];
|
||||
|
||||
yii::$app->user->identity->acpt += ['supplier' => true];
|
||||
|
||||
if (yii::$app->user->identity->save()) {
|
||||
// Удалось записать данные
|
||||
|
||||
// Запись в сессию
|
||||
yii::$app->session['offer_supplier_accepted'] = true;
|
||||
|
||||
|
||||
yii::$app->response->redirect('/');
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
18
mirzaev/skillparts/system/controllers/PartnersController.php
Normal file
18
mirzaev/skillparts/system/controllers/PartnersController.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii\web\Controller;
|
||||
|
||||
use app\models\Terminal;
|
||||
|
||||
class PartnersController extends Controller
|
||||
{
|
||||
public function actionIndex()
|
||||
{
|
||||
$terminals = Terminal::read(500);
|
||||
return $this->render('/partners/index', compact('terminals'));
|
||||
}
|
||||
}
|
15
mirzaev/skillparts/system/controllers/PolicyController.php
Normal file
15
mirzaev/skillparts/system/controllers/PolicyController.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii\web\Controller;
|
||||
|
||||
class PolicyController extends Controller
|
||||
{
|
||||
public function actionIndex()
|
||||
{
|
||||
return $this->render('/policy/index');
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,52 +10,65 @@ use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
|
||||
use app\models\AccountForm;
|
||||
use app\models\Order;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
|
||||
class RegistrationController extends Controller
|
||||
{
|
||||
// public function behaviors()
|
||||
// {
|
||||
// return [
|
||||
// 'access' => [
|
||||
// 'class' => AccessControl::class,
|
||||
// 'rules' => [
|
||||
// [
|
||||
// 'allow' => true,
|
||||
// 'roles' => ['?'],
|
||||
// ]
|
||||
// ],
|
||||
// ]
|
||||
// ];
|
||||
// }
|
||||
|
||||
public function actionIndex()
|
||||
{
|
||||
// Инициализация
|
||||
$model = new AccountForm(yii::$app->request->post('AccountForm') ?? yii::$app->request->get('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_REGISTRATION;
|
||||
|
||||
// Фильтрация
|
||||
$target = match ($target) {
|
||||
'panel' => 'panel',
|
||||
'main' => 'main',
|
||||
default => 'main'
|
||||
};
|
||||
|
||||
// Рендер для всплывающей панели
|
||||
$panel = $target === 'panel';
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
if (!yii::$app->user->isGuest || $model->registration()) {
|
||||
if ($type === 'registration' && (!yii::$app->user->isGuest || $model->registration())) {
|
||||
// Данные прошли проверку и аккаунт был создан
|
||||
|
||||
// Аутентификация
|
||||
$model->scenario = $model::SCENARIO_AUTHENTICATION;
|
||||
$model->authentication();
|
||||
|
||||
if (!$model->authentication()) {
|
||||
// Не удалось аутентифицироваться
|
||||
|
||||
yii::$app->response->statusCode = 401;
|
||||
|
||||
$model->scenario = $model::SCENARIO_REGISTRATION;
|
||||
|
||||
return [
|
||||
$target => $this->renderPartial('/account/index', compact('model', 'panel') + ['registration' => true]),
|
||||
'redirect' => '/registration',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$notifications_button = $this->renderPartial('/notification/button');
|
||||
$notifications_panel_full = true;
|
||||
$notifications_panel = $this->renderPartial('/notification/panel', compact('notifications_panel_full'));
|
||||
$notifications_panel = $this->renderPartial('/notification/panel', ['notifications_panel_full' => true]);
|
||||
$cart_button = $this->renderPartial('/cart/button', ['cart_amount' => Order::count(supplies: true)]);
|
||||
|
||||
// Запись ответа
|
||||
$return = [
|
||||
'menu' => $this->renderPartial('/account/panel/authenticated', compact(
|
||||
'notifications_button',
|
||||
'notifications_panel'
|
||||
'notifications_panel',
|
||||
'cart_button'
|
||||
)),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
@@ -65,10 +78,14 @@ class RegistrationController extends Controller
|
||||
|
||||
// Запись ответа
|
||||
$return['redirect'] = '/' . $cookies['redirect'];
|
||||
$return['main'] = $this->renderPartial($return['redirect'] . '/index');
|
||||
|
||||
// Очистка cookie
|
||||
unset(yii::$app->response->cookies['redirect']);
|
||||
|
||||
yii::$app->response->format = Response::FORMAT_HTML;
|
||||
|
||||
// Переадресация
|
||||
return $this->redirect($return['redirect']);
|
||||
} else {
|
||||
// Не найдено cookie с переадресацией
|
||||
|
||||
@@ -88,7 +105,7 @@ class RegistrationController extends Controller
|
||||
yii::$app->response->statusCode = 400;
|
||||
|
||||
return [
|
||||
'main' => $this->renderPartial('/account/index', compact('model')),
|
||||
$target => $this->renderPartial('/account/index', compact('model', 'panel') + ['registration' => true]),
|
||||
'redirect' => '/registration',
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
@@ -98,7 +115,7 @@ class RegistrationController extends Controller
|
||||
if (!yii::$app->user->isGuest) {
|
||||
yii::$app->response->redirect('/');
|
||||
} else {
|
||||
return $this->render('/account/index', compact('model'));
|
||||
return $this->render('/account/index', compact('model', 'panel') + ['registration' => true]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,22 +9,29 @@ use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
|
||||
use app\models\Product;
|
||||
use app\models\Supply;
|
||||
use app\models\Search;
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* 1. Ограничение доступа
|
||||
*/
|
||||
class SearchController extends Controller
|
||||
{
|
||||
/**
|
||||
* @todo Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать
|
||||
* @todo
|
||||
* 1. Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать
|
||||
* 2. Пагинация
|
||||
*/
|
||||
public function actionIndex(): array|string
|
||||
{
|
||||
// Инициализация параметров
|
||||
$auth_only = false;
|
||||
$account = yii::$app->user->identity;
|
||||
|
||||
if ($auth_only && yii::$app->user->isGuest) {
|
||||
// Если активирован режим "Поиск только аутентифицированным" и запрос пришел не от аутентифицированного
|
||||
|
||||
|
||||
// Запись кода ответа: 401 (необходима авторизация)
|
||||
yii::$app->response->statusCode = 401;
|
||||
|
||||
@@ -44,7 +51,7 @@ class SearchController extends Controller
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return [
|
||||
'panel' => $this->renderPartial('/search/panel', ['history' => true]),
|
||||
'panel' => $this->renderPartial('/search/panel', compact('account') + ['history' => true]),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
@@ -59,7 +66,7 @@ class SearchController extends Controller
|
||||
$timer = 0;
|
||||
|
||||
// Период пропуска запросов (в секундах)
|
||||
$period = 5;
|
||||
$period = 3;
|
||||
$keep_connect = false;
|
||||
$sanction = false;
|
||||
$sanction_condition = ($session['last_request'] + $period - time()) < $period;
|
||||
@@ -99,7 +106,7 @@ class SearchController extends Controller
|
||||
|
||||
$return = [
|
||||
'timer' => $timer,
|
||||
'panel' => $this->renderPartial('/search/loading'),
|
||||
'search_line_window_show' => 1,
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
} else {
|
||||
@@ -128,49 +135,42 @@ class SearchController extends Controller
|
||||
|
||||
$limit = yii::$app->request->isPost ? 10 : 20;
|
||||
|
||||
if ($response = Product::searchByPartialCatn($query, $limit, ['catn' => 'catn', '_key' => '_key'])) {
|
||||
if ($response = Product::searchByPartialCatn($query, 'active', $limit, [
|
||||
'_key' => '_key',
|
||||
'catn' => 'catn',
|
||||
'prod' => 'prod',
|
||||
// Баг с названием DESC
|
||||
'dscr' => 'dscr',
|
||||
'catg' => 'catg',
|
||||
'imgs' => 'imgs',
|
||||
'name' => 'name',
|
||||
'prod' => 'prod',
|
||||
'dmns' => 'dmns',
|
||||
'stts' => 'stts'
|
||||
])) {
|
||||
// Данные найдены по поиску в полях Каталожного номера
|
||||
|
||||
foreach ($response as &$row) {
|
||||
// Перебор продуктов
|
||||
// Генерация данных для представления
|
||||
$response = Search::content(products: $response);
|
||||
|
||||
// Поиск поставок привязанных к продуктам
|
||||
$row['supplies'] = Supply::searchByEdge(
|
||||
from: 'product',
|
||||
to: 'supply',
|
||||
edge: 'supply_edge_product',
|
||||
limit: 11,
|
||||
direction: 'OUTBOUND',
|
||||
subquery_where: [
|
||||
['product._key' => $row['_key']],
|
||||
['supply.catn == product.catn'],
|
||||
['supply_edge_product.type' => 'connect']
|
||||
],
|
||||
where: 'supply._id == supply_edge_product[0]._from',
|
||||
select: 'supply_edge_product[0]'
|
||||
);
|
||||
|
||||
if (count($row['supplies']) === 11) {
|
||||
// Если в базе данных хранится много поставок
|
||||
|
||||
// Инициализация
|
||||
$row['overload'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Запись ответа
|
||||
$return = [
|
||||
'panel' => $this->renderPartial('/search/panel', compact('response')),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
|
||||
if ((int) yii::$app->request->post('advanced')) {
|
||||
// Полноценный поиск
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
// Запись ответа
|
||||
$return['main'] = $this->renderPartial('/search/index', compact('response'));
|
||||
$return['hide'] = 1;
|
||||
$return['redirect'] = '/search?type=product&q=' . $query;
|
||||
$return = [
|
||||
'panel' => $this->renderPartial('/search/panel', compact('response', 'query')),
|
||||
'search_line_window_show' => 1,
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
|
||||
if ((int) yii::$app->request->post('advanced')) {
|
||||
// Полноценный поиск
|
||||
|
||||
// Запись ответа
|
||||
$return['main'] = $this->renderPartial('/search/index', compact('response', 'query'));
|
||||
$return['search_line_window_show'] = 1;
|
||||
$return['redirect'] = '/search?type=product&q=' . $query;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Данные не найдены
|
||||
@@ -190,6 +190,7 @@ class SearchController extends Controller
|
||||
|
||||
return $return ?? [
|
||||
'panel' => $this->renderPartial('/search/panel'),
|
||||
'search_line_window_show' => 1,
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
} else {
|
||||
@@ -205,7 +206,9 @@ class SearchController extends Controller
|
||||
goto keep_connect_wait;
|
||||
}
|
||||
|
||||
return $this->render('/search/index', compact('response', 'timer'));
|
||||
$advanced = true;
|
||||
|
||||
return $this->render('/search/index', compact('response', 'timer', 'advanced', 'query'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii;
|
||||
use yii\web\Controller;
|
||||
use yii\web\UploadedFile;
|
||||
|
||||
use app\models\Request;
|
||||
use app\models\Account;
|
||||
|
||||
class SuppliersController extends Controller
|
||||
{
|
||||
public function actionIndex()
|
||||
{
|
||||
return $this->renderPartial('/suppliers/index');
|
||||
}
|
||||
|
||||
public function actionRequest()
|
||||
{
|
||||
return $this->renderPartial('/suppliers/request');
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Сделать отправку только назначенным модераторам
|
||||
*/
|
||||
public function actionRequestSend()
|
||||
{
|
||||
// Инициализация данных запроса
|
||||
$request = yii::$app->request->post('Request') ?? yii::$app->request->get('Request');
|
||||
|
||||
// Запись поставщика
|
||||
Account::writeSupplier($request['name'], $request['phon'], $request['mail'], $file = UploadedFile::getInstance(new Request($request), 'file'));
|
||||
|
||||
yii::$app->mail_system->compose()
|
||||
->setFrom(yii::$app->params['mail']['system'])
|
||||
->setTo(yii::$app->params['mail']['info'])
|
||||
->setSubject('Регистрация поставщика')
|
||||
->setHtmlBody($this->renderPartial('/mails/supplier', $request))
|
||||
->attach($file->tempName, ['fileName' => $file->name])
|
||||
->send();
|
||||
|
||||
return $this->renderPartial('/suppliers/requested');
|
||||
}
|
||||
}
|
101
mirzaev/skillparts/system/controllers/SupplyController.php
Normal file
101
mirzaev/skillparts/system/controllers/SupplyController.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
use yii\web\HttpException;
|
||||
use yii\web\UploadedFile;
|
||||
use yii\filters\AccessControl;
|
||||
|
||||
use app\models\Product;
|
||||
use app\models\Settings;
|
||||
use app\models\SupplyEdgeProduct;
|
||||
use app\models\Supply;
|
||||
use app\models\Account;
|
||||
use app\models\Notification;
|
||||
use app\models\OrderEdgeSupply;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SupplyController extends Controller
|
||||
{
|
||||
public function behaviors()
|
||||
{
|
||||
return [
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'rules' => [
|
||||
[
|
||||
'allow' => true,
|
||||
'actions' => [
|
||||
'index',
|
||||
]
|
||||
],
|
||||
[
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
'actions' => []
|
||||
],
|
||||
[
|
||||
'allow' => true,
|
||||
'actions' => [
|
||||
'read'
|
||||
],
|
||||
'matchCallback' => function ($rule, $action): bool {
|
||||
if (
|
||||
!yii::$app->user->isGuest
|
||||
&& (yii::$app->user->identity->type === 'administrator'
|
||||
|| yii::$app->user->identity->type === 'moderator')
|
||||
) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
],
|
||||
[
|
||||
'allow' => false,
|
||||
'roles' => ['?'],
|
||||
'denyCallback' => [$this, 'accessDenied']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение поставок
|
||||
*
|
||||
* @param int $page Страница
|
||||
*
|
||||
* @return string|array|null
|
||||
*/
|
||||
public function actionRead(int $page = 1): string|array|null
|
||||
{
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
// Инициализация входных параметров
|
||||
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 20;
|
||||
$order = yii::$app->request->post('order') ?? yii::$app->request->get('order') ?? ['DESC'];
|
||||
|
||||
// Инициализация cookie
|
||||
$cookies = yii::$app->response->cookies;
|
||||
|
||||
// Чтение поставок
|
||||
$supplies = Supply::read(limit: $amount, page: $page, order: $order);
|
||||
|
||||
// Запись формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return [
|
||||
'supplies' => $this->renderPartial('/supply/list', compact('supplies', 'amount', 'page')),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
43
mirzaev/skillparts/system/controllers/TerminalController.php
Normal file
43
mirzaev/skillparts/system/controllers/TerminalController.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
|
||||
use app\models\Terminal;
|
||||
|
||||
class TerminalController extends Controller
|
||||
{
|
||||
public function actionRead(): ?array
|
||||
{
|
||||
// Инициализация входных параметров
|
||||
$amount = (int) yii::$app->request->post('amount') ?? yii::$app->request->get('amount');
|
||||
|
||||
if ($amount < 501) {
|
||||
// Пройдена проверка
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
// Запись формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
return [
|
||||
'terminals' => Terminal::read($amount),
|
||||
'_csrf' => yii::$app->request->getCsrfToken()
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// Не пройдена проверка
|
||||
|
||||
// Запись кода ответа
|
||||
yii::$app->response->statusCode = 500;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
125
mirzaev/skillparts/system/controllers/VerifyController.php
Normal file
125
mirzaev/skillparts/system/controllers/VerifyController.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\controllers;
|
||||
|
||||
use yii;
|
||||
use yii\web\Controller;
|
||||
use yii\web\Response;
|
||||
|
||||
use app\models\Account;
|
||||
|
||||
class VerifyController extends Controller
|
||||
{
|
||||
public function actionIndex(string $vrfy = null): string|Response
|
||||
{
|
||||
if (isset($vrfy)) {
|
||||
// Подтверждение регистрации
|
||||
|
||||
if (Account::verification($vrfy, auth: true)) {
|
||||
// Успешно подтверждена регистрация
|
||||
|
||||
return $this->redirect('/profile');
|
||||
}
|
||||
|
||||
return ErrorController::throw('Ошибка подтверждения', 'Код не совпадает с тем, что мы отправили вам на почту, либо регистрация уже была подтверждена.\n Свяжитесь с администрацией');
|
||||
} else {
|
||||
// Простой запрос
|
||||
|
||||
if (yii::$app->user->isGuest) {
|
||||
// Пользователь не аутентифицирован
|
||||
|
||||
return yii::$app->response->redirect('/registration');
|
||||
} else {
|
||||
// Пользователь аутентифицирован
|
||||
|
||||
if (yii::$app->user->identity->vrfy === true) {
|
||||
// Регистрация аккаунта уже подтверждена
|
||||
|
||||
// Генерация хеша пароля
|
||||
yii::$app->user->identity->pswd = yii::$app->security->generatePasswordHash(yii::$app->user->identity->pswd);
|
||||
|
||||
// Запись в хранилище
|
||||
yii::$app->user->identity->update();
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправить запрос на активацию
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function actionSend(): string|array
|
||||
{
|
||||
if (!yii::$app->user->isGuest) {
|
||||
// Пользователь аутентифицирован
|
||||
|
||||
// Регенерация кода подтверждения
|
||||
yii::$app->user->identity->verifyRegenerate();
|
||||
|
||||
// Отправка кода подтверждения на почту
|
||||
yii::$app->user->identity->sendMailVerify();
|
||||
|
||||
if (yii::$app->request->isPost) {
|
||||
// POST-запрос
|
||||
|
||||
// Запись формата ответа
|
||||
yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
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()
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m210502_102203_create_terminal_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('terminal', ['type' => 2]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('terminal');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m210502_102358_create_dellin_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('dellin', ['type' => 2]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('dellin');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m210510_180939_create_request_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('request', ['type' => 2]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('request');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m210512_121658_create_cdek_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('cdek', ['type' => 2]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('cdek');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m211123_114511_create_import_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('import', ['type' => 2]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('import');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m211123_120136_create_import_edge_supply_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('import_edge_supply', ['type' => 3]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('import_edge_supply');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m211123_173801_create_import_edge_account_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('import_edge_account', ['type' => 3]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('import_edge_account');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m211221_183410_create_warehouse_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('warehouse', ['type' => 2]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('warehouse');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m211221_183447_create_warehouse_edge_import_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('warehouse_edge_import', ['type' => 3]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('warehouse_edge_import');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m211221_193454_create_account_edge_warehouse_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('account_edge_warehouse', ['type' => 3]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('account_edge_warehouse');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m220808_185553_create_file_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('file', ['type' => 2]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('file');
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use mirzaev\yii2\arangodb\Migration;
|
||||
|
||||
class m220817_210350_create_import_edge_file_collection extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
/**
|
||||
* @param string Название коллекции
|
||||
* @param array Тип коллекции (2 - документ, 3 - ребро)
|
||||
*/
|
||||
$this->createCollection('import_edge_file', ['type' => 3]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->dropCollection('import_edge_file');
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -10,4 +10,79 @@ class AccountEdgeOrder extends Edge
|
||||
{
|
||||
return 'account_edge_order';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'stts'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'stts' => 'Статус'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
[
|
||||
'stts'
|
||||
],
|
||||
'string'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Перед сохранением
|
||||
*
|
||||
* @todo Подождать обновление от ебаного Yii2 и добавить
|
||||
* проверку типов передаваемых параметров
|
||||
*/
|
||||
public function beforeSave($data): bool
|
||||
{
|
||||
if (parent::beforeSave($data)) {
|
||||
if ($this->isNewRecord) {
|
||||
$this->stts = 'current';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по идентификатору заказа
|
||||
*
|
||||
* @param string $order_id Идентификатор записи заказа в базе данных
|
||||
* @param int $limit Ограничение
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public static function searchByOrder(string $order_id, int $limit = 1): ?array
|
||||
{
|
||||
return self::find()->where(['_to' => $order_id])->limit($limit)->all();
|
||||
}
|
||||
}
|
||||
|
21
mirzaev/skillparts/system/models/AccountEdgeWarehouse.php
Normal file
21
mirzaev/skillparts/system/models/AccountEdgeWarehouse.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use app\models\Account;
|
||||
|
||||
/**
|
||||
* Связь аккаунтов и складов
|
||||
*/
|
||||
class AccountEdgeWarehouse extends Edge
|
||||
{
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'account_edge_warehouse';
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@ use yii;
|
||||
use yii\base\Model;
|
||||
|
||||
use app\models\Account;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* AccountForm is the model behind the login form.
|
||||
@@ -24,6 +25,8 @@ class AccountForm extends Model
|
||||
public $mail;
|
||||
public $pswd;
|
||||
public $auto = false;
|
||||
public $rept;
|
||||
public $pols;
|
||||
|
||||
private $account = false;
|
||||
|
||||
@@ -32,14 +35,45 @@ class AccountForm extends Model
|
||||
{
|
||||
return [
|
||||
// Обязательные поля
|
||||
[['mail', 'pswd'], 'required', 'message' => 'Заполните поле'],
|
||||
[
|
||||
[
|
||||
'mail',
|
||||
],
|
||||
'required',
|
||||
'message' => 'Заполните поле'
|
||||
],
|
||||
// Обязательные поля для аутентификации
|
||||
[
|
||||
[
|
||||
'pswd',
|
||||
],
|
||||
'required',
|
||||
'message' => 'Заполните поле',
|
||||
'on' => self::SCENARIO_AUTHENTICATION
|
||||
],
|
||||
// Функция "Запомнить меня"
|
||||
['auto', 'boolean', 'on' => self::SCENARIO_AUTHENTICATION],
|
||||
[
|
||||
'auto',
|
||||
'boolean',
|
||||
'on' => self::SCENARIO_AUTHENTICATION
|
||||
],
|
||||
// Проверка почты,
|
||||
['mail', 'email', 'message' => 'Проверьте почту'],
|
||||
['mail', 'validateMail', 'on' => self::SCENARIO_REGISTRATION],
|
||||
[
|
||||
'mail',
|
||||
'email',
|
||||
'message' => 'Проверьте почту'
|
||||
],
|
||||
[
|
||||
'mail',
|
||||
'validateMail',
|
||||
'on' => self::SCENARIO_REGISTRATION
|
||||
],
|
||||
// Проверка пароля
|
||||
['pswd', 'validatePassword', 'on' => self::SCENARIO_AUTHENTICATION]
|
||||
[
|
||||
'pswd',
|
||||
'validatePassword',
|
||||
'on' => self::SCENARIO_AUTHENTICATION
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -48,7 +82,8 @@ class AccountForm extends Model
|
||||
return [
|
||||
'mail' => 'Почта',
|
||||
'pswd' => 'Пароль',
|
||||
'auto' => '<i class="fas fa-lock"></i>'
|
||||
'auto' => '<i class="fas fa-unlock"></i>',
|
||||
'pols' => 'Политика конфедециальности'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -59,6 +94,12 @@ class AccountForm extends Model
|
||||
|
||||
$account = $this->getAccount();
|
||||
|
||||
if (is_null($account)) {
|
||||
// Не удалось проверить аккаунт
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$account || $account->validateMail($this->mail)) {
|
||||
// Проверка не пройдена
|
||||
|
||||
@@ -74,10 +115,38 @@ class AccountForm extends Model
|
||||
|
||||
$account = $this->getAccount();
|
||||
|
||||
if (!$account || !$account->validatePassword($this->pswd)) {
|
||||
// Проверка не пройдена
|
||||
if (is_null($account)) {
|
||||
// Не удалось проверить аккаунт
|
||||
|
||||
$this->addError($attribute, 'Проверьте пароль');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($account) {
|
||||
// Удалось инициализировать аккаунт
|
||||
|
||||
try {
|
||||
if (!$account->validatePasswordWithHash($this->pswd)) {
|
||||
// Не пройдена проверка с хешем
|
||||
|
||||
throw new exception;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Не пройдена проверка с хешем
|
||||
|
||||
try {
|
||||
if (!$account->validatePasswordWithoutHash($this->pswd)) {
|
||||
// Не пройдена проверка с паролем
|
||||
|
||||
throw new exception;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Проверка без хеша не пройдена
|
||||
|
||||
$this->addError($attribute, 'Проверьте пароль');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->addError($attribute, 'Не удалось идентифицировать аккаунт');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +162,9 @@ class AccountForm extends Model
|
||||
$this->pswd = $pswd;
|
||||
}
|
||||
|
||||
// Регистронезависимая почта
|
||||
$this->mail = mb_strtolower($this->mail);
|
||||
|
||||
if (isset($this->mail, $this->pswd) && $this->validate()) {
|
||||
// Проверка пройдена
|
||||
|
||||
@@ -111,15 +183,33 @@ class AccountForm extends Model
|
||||
// Инициализация нового аккаунта
|
||||
$this->account = new Account();
|
||||
|
||||
if (isset($this->mail, $this->pswd) && $this->validate()) {
|
||||
if ($this->validate()) {
|
||||
// Проверка пройдена
|
||||
|
||||
// Запись параметров
|
||||
$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();
|
||||
}
|
||||
|
||||
// Регистрация
|
||||
return $this->account->save();
|
||||
if ($this->account->save()) {
|
||||
// Успешно завершена регистрация или обновлены данные не до конца зарегистрировавшегося пользователя
|
||||
|
||||
// Генерация индекса
|
||||
Account::generateIndexes([$this->account]);
|
||||
|
||||
// Отправка письма для подтверждения почты
|
||||
$this->account->sendMailVerify();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
43
mirzaev/skillparts/system/models/Cdek.php
Normal file
43
mirzaev/skillparts/system/models/Cdek.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use CdekSDK2\Client;
|
||||
|
||||
class Cdek extends Document
|
||||
{
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'cdek';
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'data'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'data' => 'Данные ДеловыеЛинии',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по идентификатору города
|
||||
*/
|
||||
public static function searchByCityId(string $id): ?static
|
||||
{
|
||||
return static::findOne(['data["id"]' => $id]);
|
||||
}
|
||||
}
|
70
mirzaev/skillparts/system/models/Dellin.php
Normal file
70
mirzaev/skillparts/system/models/Dellin.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use ArangoDBClient\Document as ArangoDBDocument;
|
||||
|
||||
class Dellin extends Document
|
||||
{
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'dellin';
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'data'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'data' => 'Данные ДеловыеЛинии',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по идентификатору города
|
||||
*
|
||||
* @param string $id Идентификатор города
|
||||
*/
|
||||
public static function searchByCityId(string $id): ?static
|
||||
{
|
||||
return static::findOne(['data["id"]' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по КЛАДР-коду города
|
||||
*
|
||||
* @param string $id Код КЛАДР города
|
||||
*/
|
||||
public static function searchByCityKladr(string $code): ?static
|
||||
{
|
||||
return static::findOne(['data["code"]' => $code]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по идентификатору терминала
|
||||
*
|
||||
* @param int $id Идентификатор терминала
|
||||
* @param bool $terminal_data_only Запрос только данных терминала
|
||||
*/
|
||||
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];
|
||||
}
|
||||
|
||||
return static::find()->foreach(['terminal' => self::collectionName() . '.data["terminals"]["terminal"]'])->where(['terminal["id"] == "' . $id . '"'])->one();
|
||||
}
|
||||
}
|
@@ -20,7 +20,9 @@ abstract class Document extends ActiveRecord
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return throw new Exception('Не инициализировано название коллекции');
|
||||
throw new Exception('Не инициализировано название коллекции');
|
||||
|
||||
return 'document';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,15 +64,19 @@ abstract class Document extends ActiveRecord
|
||||
* @todo Подождать обновление от ебаного Yii2 и добавить
|
||||
* проверку типов передаваемых параметров
|
||||
*/
|
||||
public function beforeSave($data): bool
|
||||
public function beforeSave($create): bool
|
||||
{
|
||||
if (parent::beforeSave($data)) {
|
||||
if (parent::beforeSave($create)) {
|
||||
// Пройдена родительская проверка
|
||||
|
||||
if ($this->isNewRecord) {
|
||||
// Новая запись
|
||||
|
||||
// Запись в журнал
|
||||
$this->jrnl = array_merge(
|
||||
[[
|
||||
'date' => time(),
|
||||
'account' => yii::$app->user->id,
|
||||
'account' => yii::$app->user->id ?? 'system',
|
||||
'action' => 'create'
|
||||
]],
|
||||
$this->jrnl ?? []
|
||||
@@ -106,10 +112,12 @@ abstract class Document extends ActiveRecord
|
||||
[array_merge(
|
||||
[
|
||||
'date' => $time = time(),
|
||||
'account' => yii::$app->user->id,
|
||||
'account' => yii::$app->user->id ?? 'system',
|
||||
'action' => $action
|
||||
],
|
||||
...$data
|
||||
[
|
||||
'data' => $data
|
||||
]
|
||||
)]
|
||||
);
|
||||
|
||||
@@ -125,6 +133,15 @@ abstract class Document extends ActiveRecord
|
||||
return isset($this->_key) && static::collectionName() ? static::collectionName() . '/' . $this->_key : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск
|
||||
*/
|
||||
public static function search(array $where, $limit = 1): array
|
||||
{
|
||||
return static::find()->where($where)->limit($limit)->all();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Поиск по идентификатору
|
||||
*/
|
||||
@@ -133,19 +150,29 @@ abstract class Document extends ActiveRecord
|
||||
return static::findOne(['_id' => $_id]);
|
||||
}
|
||||
|
||||
public static function readLast(): ?static
|
||||
public static function readLast(): static|null|bool
|
||||
{
|
||||
return static::find()->orderBy(['DESC'])->one();
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение всех записей
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static function readAll(): array
|
||||
{
|
||||
return static::find()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение записей по максимальному ограничению
|
||||
*/
|
||||
public static function read(?array $where = [], int $limit = 100, int $page = 1, ?array $order = null): array
|
||||
{
|
||||
return static::find()->where($where)->orderby($order)->limit($limit)->offset(($page - 1) * $limit)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение количества записей
|
||||
*/
|
||||
@@ -157,12 +184,61 @@ abstract class Document extends ActiveRecord
|
||||
/**
|
||||
* Проверка на то, что в свойство передан массив
|
||||
*/
|
||||
public function arrayValidator(string $attribute, array $params = null): bool
|
||||
public function arrayValidator(string $attribute, array $params = null): void
|
||||
{
|
||||
if (is_array($this->$attribute)) {
|
||||
return true;
|
||||
return;
|
||||
} else {
|
||||
$this->addError($attribute, 'Передан не массив');
|
||||
}
|
||||
|
||||
return false;
|
||||
$this->addError($attribute, 'Не пройдена проверка: "arrayValidator"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка на то, что в свойство передан массив и он хранит циферные значения
|
||||
*/
|
||||
public function arrayWithNumbersValidator(string $attribute, array $params = null): void
|
||||
{
|
||||
try {
|
||||
if (is_array($this->$attribute)) {
|
||||
|
||||
foreach ($this->$attribute as $value) {
|
||||
if (!(bool) preg_match('/^[0-9\.]*$/m', $value)) {
|
||||
$this->addError($attribute, 'В массиве найдены запрещённые символы');
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
$this->addError($attribute, 'Передан не массив');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->addError($attribute, $e->getMessage());
|
||||
}
|
||||
|
||||
$this->addError($attribute, 'Не пройдена проверка: "arrayWithNumbersValidator"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Конвертировать _id в _key
|
||||
*/
|
||||
private static function keyFromId(string $_id): ?string
|
||||
{
|
||||
preg_match_all('/\/([0-9]+)$/m', $_id, $mathes);
|
||||
|
||||
return $mathes[1][0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Статический вызов
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $args
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args): mixed {
|
||||
return match ($name) {
|
||||
'keyFromId' => self::keyFromId(...$args)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -81,13 +81,21 @@ abstract class Edge extends Document
|
||||
* Записать (с проверкой на существование)
|
||||
*
|
||||
* Создаст ребро только в том случае, если его аналога не существует
|
||||
*
|
||||
* @param string $_from Идентификатор отправителя (_id)
|
||||
* @param string $_from Идентификатор получетеля (_id)
|
||||
* @param string $type Дополнительное поле - тип взаимосвязи
|
||||
* @param string $data Дополнительные данные
|
||||
*
|
||||
* @todo
|
||||
* 1. Удалить $type и оставить только $data
|
||||
*/
|
||||
public static function writeSafe(string $_from, string $_to, string $type = '', array $data = []): ?static
|
||||
{
|
||||
if ($edge = self::searchByVertex($_from, $_to, limit: 1)) {
|
||||
// Найдено в базе данных
|
||||
|
||||
return $edge;
|
||||
return $edge[0];
|
||||
}
|
||||
|
||||
return self::write($_from, $_to, $type, $data);
|
||||
@@ -95,8 +103,16 @@ abstract class Edge extends Document
|
||||
|
||||
/**
|
||||
* Записать
|
||||
*
|
||||
* @param string $_from Идентификатор отправителя (_id)
|
||||
* @param string $_from Идентификатор получетеля (_id)
|
||||
* @param string $type Дополнительное поле - тип взаимосвязи @deprecated
|
||||
* @param string $data Дополнительные данные
|
||||
*
|
||||
* @todo
|
||||
* 1. Удалить $type и оставить только $data
|
||||
*/
|
||||
public static function write(string $_from, string $_to, string $type, array $data = []): ?static
|
||||
public static function write(string $_from, string $_to, string $type = '', array $data = []): ?static
|
||||
{
|
||||
// Инициализация
|
||||
$edge = new static;
|
||||
@@ -104,7 +120,7 @@ abstract class Edge extends Document
|
||||
// Настройка
|
||||
$edge->_from = $_from;
|
||||
$edge->_to = $_to;
|
||||
$edge->type = $type;
|
||||
if (isset($type)) $edge->type = $type;
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_int($key)) {
|
||||
@@ -123,8 +139,14 @@ abstract class Edge extends Document
|
||||
|
||||
/**
|
||||
* Поиск ребра по его вершинам
|
||||
*
|
||||
* @param string $_from Идентификатор исходящей вершины
|
||||
* @param string $_to Идентификатор входящей вершины
|
||||
* @param string|null $type deprecated
|
||||
* @param int $limit Ограничение по количеству
|
||||
* @param array|null $filter Фильтр (where())
|
||||
*/
|
||||
public static function searchByVertex(string $_from, string $_to, string|null $type = null, int $limit = 1): array|null
|
||||
public static function searchByVertex(string $_from, string $_to, string|null $type = null, int $limit = 1, array|null $filter = null): array|null
|
||||
{
|
||||
$query = self::find()->where([
|
||||
'_from' => $_from,
|
||||
@@ -135,24 +157,64 @@ abstract class Edge extends Document
|
||||
$query->where(['type' => $type]);
|
||||
}
|
||||
|
||||
if (isset($filter)) {
|
||||
$query->where($filter);
|
||||
}
|
||||
|
||||
return $query->limit($limit)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск рёбер
|
||||
* Поиск рёбер по направлению
|
||||
*/
|
||||
public static function search(string $target, string $direction = 'OUTBOUND', string $type = '', int $limit = 1): static|array|null
|
||||
public static function searchByDirection(string $_from, string $direction = 'OUTBOUND', string $type = '', array $where = [], int $limit = 1, int $page = 1): static|array|null
|
||||
{
|
||||
if ($direction === 'OUTBOUND') {
|
||||
$query = self::find()->where(['_from' => $target, 'type' => $type]);
|
||||
} else if ($direction === 'INBOUND') {
|
||||
$query = self::find()->where(['_to' => $target, 'type' => $type]);
|
||||
if (str_contains($direction, 'OUTBOUND')) {
|
||||
// Исходящие рёбра
|
||||
|
||||
if (isset($where)) {
|
||||
// Получен параметр $where
|
||||
|
||||
// Реинициализация
|
||||
$where = array_merge($where + ['_from' => $_from]);
|
||||
} else {
|
||||
// Не получен параметр $where
|
||||
|
||||
// Реинициализация
|
||||
$where = array_merge(['_from' => $_from, 'type' => $type]);
|
||||
}
|
||||
|
||||
$query = static::find()->where($where);
|
||||
} else if (str_contains($direction, 'INBOUND')) {
|
||||
// Входящие рёбра
|
||||
|
||||
if (isset($where)) {
|
||||
// Получен параметр $where
|
||||
|
||||
// Реинициализация
|
||||
$where = array_merge($where + ['_to' => $_from]);
|
||||
} else {
|
||||
// Не получен параметр $where
|
||||
|
||||
// Реинициализация
|
||||
$where = array_merge(['_to' => $_from, 'type' => $type]);
|
||||
}
|
||||
|
||||
$query = static::find()->where($where);
|
||||
} else if (str_contains($direction, 'ANY')) {
|
||||
// Исходящие и входящие рёбра
|
||||
|
||||
return static::searchByDirection(_from: $_from, direction: 'OUTBOUND', type: $type, where: $where, limit: $limit, page: $page) + static::searchByDirection(_from: $_from, direction: 'INBOUND', type: $type, where: $where, limit: $limit, page: $page);
|
||||
}
|
||||
|
||||
if ($limit < 2) {
|
||||
// Одно ребро или меньше
|
||||
|
||||
return $query->one();
|
||||
} else {
|
||||
return $query->limit($limit)->all();
|
||||
// Несколько рёбер
|
||||
|
||||
return $query->limit($limit)->offset($limit * ($page - 1))->all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
100
mirzaev/skillparts/system/models/File.php
Normal file
100
mirzaev/skillparts/system/models/File.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use ArangoDBClient\Document as ArangoDBDocument;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
use app\models\ImportEdgeFile;
|
||||
|
||||
class File extends Document
|
||||
{
|
||||
use SearchByEdge;
|
||||
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'file';
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'type',
|
||||
'path',
|
||||
'name',
|
||||
'user',
|
||||
'stts',
|
||||
'meta'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'type' => 'Тип файла',
|
||||
'path' => 'Относительный от хранилища путь до файла',
|
||||
'name' => 'Название файла',
|
||||
'user' => 'Пользователь управляющий файлом',
|
||||
'stts' => 'Статус',
|
||||
'meta' => 'Метаданные'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Перед сохранением
|
||||
*
|
||||
* @todo Подождать обновление от ебаного Yii2 и добавить
|
||||
* проверку типов передаваемых параметров
|
||||
*/
|
||||
public function beforeSave($data): bool
|
||||
{
|
||||
if (parent::beforeSave($data)) {
|
||||
if ($this->isNewRecord) {
|
||||
if ($this->type = 'supplies excel') {
|
||||
// Список поставок
|
||||
|
||||
$this->stts = 'needed to load';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function searchSuppliesNeededToLoad(int $amount = 3): array
|
||||
{
|
||||
return static::find()->where(['stts' => 'needed to load'])->limit($amount)->all();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Поиск по инстанции импорта
|
||||
*
|
||||
* @param Import $import Инстанция импорта
|
||||
*/
|
||||
public static function searchByImport(Import $import): ?File
|
||||
{
|
||||
return new File(self::searchByEdge(
|
||||
from: 'import',
|
||||
to: 'file',
|
||||
subquery_where: [
|
||||
[
|
||||
'import._id' => $import->readId()
|
||||
]
|
||||
],
|
||||
where: 'import_edge_file[0]._id != null',
|
||||
select: 'file',
|
||||
limit: 1
|
||||
)[0]) ?? null;
|
||||
}
|
||||
}
|
146
mirzaev/skillparts/system/models/Import.php
Normal file
146
mirzaev/skillparts/system/models/Import.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use yii;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
use app\models\Account;
|
||||
|
||||
/**
|
||||
* Импорт поставок
|
||||
*
|
||||
* Хранит в себе связи с поставками которые были загружены вместе
|
||||
*/
|
||||
class Import extends Document
|
||||
{
|
||||
use SearchByEdge;
|
||||
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'import';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'name',
|
||||
'file'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'name' => 'Название',
|
||||
'file' => 'Файл'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
[
|
||||
'file',
|
||||
'name'
|
||||
],
|
||||
'string'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по складу
|
||||
*
|
||||
* @param Warehouse $warehouse Инстанция склада
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*
|
||||
* @return array Инстанции импортов
|
||||
*/
|
||||
public static function searchByWarehouse(Warehouse $warehouse, int $limit = 10): ?array
|
||||
{
|
||||
return self::searchByEdge(
|
||||
from: 'warehouse',
|
||||
to: 'import',
|
||||
edge: 'warehouse_edge_import',
|
||||
direction: 'INBOUND',
|
||||
subquery_where: [
|
||||
['warehouse_edge_import._from' => $warehouse->readId()],
|
||||
['warehouse_edge_import.type' => 'loaded']
|
||||
],
|
||||
where: 'warehouse_edge_import[0] != null',
|
||||
limit: $limit
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по поставке
|
||||
*
|
||||
* @param Supply $supply Поставка
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*
|
||||
* @return array Инстанции импортов
|
||||
*/
|
||||
public static function searchBySupply(Supply $supply, int $limit = 10): ?array
|
||||
{
|
||||
return self::searchByEdge(
|
||||
from: 'supply',
|
||||
to: 'import',
|
||||
edge: 'import_edge_supply',
|
||||
direction: 'OUTBOUND',
|
||||
subquery_where: [
|
||||
['import_edge_supply._to' => $supply->readId()],
|
||||
['import_edge_supply.type' => 'imported']
|
||||
],
|
||||
where: 'import_edge_supply[0] != null',
|
||||
limit: $limit
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по файлу
|
||||
*
|
||||
* @param File $file Файл
|
||||
*
|
||||
* @return Import|null Инстанция импорта
|
||||
*/
|
||||
public static function searchByFile(File $file): ?Import
|
||||
{
|
||||
return self::searchByEdge(
|
||||
from: 'file',
|
||||
to: 'import',
|
||||
edge: 'import_edge_file',
|
||||
direction: 'OUTBOUND',
|
||||
subquery_where: [
|
||||
['import_edge_file._to' => $file->readId()],
|
||||
['import_edge_supply.type' => 'connected']
|
||||
],
|
||||
where: 'import_edge_supply[0] != null',
|
||||
limit: 1
|
||||
)[0] ?? null;
|
||||
}
|
||||
}
|
70
mirzaev/skillparts/system/models/ImportEdgeAccount.php
Normal file
70
mirzaev/skillparts/system/models/ImportEdgeAccount.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use app\models\Account;
|
||||
|
||||
/**
|
||||
* Связь инстанций импорта с поставками
|
||||
*/
|
||||
class ImportEdgeAccount extends Edge
|
||||
{
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'import_edge_account';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по аккаунту
|
||||
*
|
||||
* @param Account $account Аккаунт
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*/
|
||||
public static function searchByAccount(Account $account, int $limit = 1): array
|
||||
{
|
||||
$account = Account::initAccount($account);
|
||||
|
||||
return static::find()->where(['_from' => $account->collectionName() . "$account->_key"])->limit($limit)->all();
|
||||
}
|
||||
}
|
80
mirzaev/skillparts/system/models/ImportEdgeFile.php
Normal file
80
mirzaev/skillparts/system/models/ImportEdgeFile.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use app\models\File;
|
||||
use app\models\Import;
|
||||
|
||||
/**
|
||||
* Связь инстанций импорта с поставками
|
||||
*/
|
||||
class ImportEdgeFile extends Edge
|
||||
{
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'import_edge_file';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по файлу
|
||||
*
|
||||
* @param File $file Файл
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*/
|
||||
public static function searchByFile(File $file, int $limit = 1): array
|
||||
{
|
||||
return static::find()->where(['_to' => $file->readId(), 'type' => 'connected'])->limit($limit)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по инстанции импорта
|
||||
*
|
||||
* @param Import $import Инстанция импорта
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*/
|
||||
public static function searchByImport(Import $import, int $limit = 1): array
|
||||
{
|
||||
return static::find()->where(['_from' => $import->readId(), 'type' => 'connected'])->limit($limit)->all();
|
||||
}
|
||||
}
|
115
mirzaev/skillparts/system/models/ImportEdgeSupply.php
Normal file
115
mirzaev/skillparts/system/models/ImportEdgeSupply.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use mirzaev\yii2\arangodb\Query;
|
||||
|
||||
/**
|
||||
* Связь инстанций импорта с поставками
|
||||
*/
|
||||
class ImportEdgeSupply extends Edge
|
||||
{
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'import_edge_supply';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'vrsn'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'vrsn' => 'Версия'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
[
|
||||
'vrsn'
|
||||
],
|
||||
'integer',
|
||||
'message' => '{attribute} должен быть числом.'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Перед сохранением
|
||||
*
|
||||
* @todo Подождать обновление от ебаного Yii2 и добавить
|
||||
* проверку типов передаваемых параметров
|
||||
*/
|
||||
public function beforeSave($data): bool
|
||||
{
|
||||
if (parent::beforeSave($data)) {
|
||||
if ($this->isNewRecord) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск максимальной версии
|
||||
*
|
||||
* Ищет максимальную версию у поставок
|
||||
*
|
||||
* @param Supply $supply Поставка
|
||||
*
|
||||
* @return int Версия, если найдена
|
||||
*/
|
||||
public static function searchMaxVersion(Supply $supply): ?int
|
||||
{
|
||||
return static::find()->execute("RETURN MAX(
|
||||
FOR import_edge_supply IN import_edge_supply
|
||||
FILTER (import_edge_supply._to == 'supply/$supply->_key')
|
||||
LIMIT 0,999
|
||||
RETURN import_edge_supply.vrsn
|
||||
)")[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по поставке
|
||||
*
|
||||
* @param Supply $supply
|
||||
*
|
||||
* @return static|null
|
||||
*/
|
||||
public static function searchBySupply(Supply $supply): ?static
|
||||
{
|
||||
return static::find()->where(['_to' => $supply->readId()])->one() ?? null;
|
||||
}
|
||||
}
|
40
mirzaev/skillparts/system/models/Invoice.php
Normal file
40
mirzaev/skillparts/system/models/Invoice.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use yii;
|
||||
use yii\base\Model;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
// use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
|
||||
// use PhpOffice\PhpSpreadsheet\Writer\Html;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Html as HtmlReader;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Invoice extends Model
|
||||
{
|
||||
public static function generate(string|int $order_key, string $html): void
|
||||
{
|
||||
// Инициализация директории
|
||||
$dir = YII_PATH_PUBLIC . '/../assets/invoices/' . $order_key;
|
||||
|
||||
// Сохранение на диск
|
||||
if (!file_exists($dir) && !mkdir($dir, 0775, true)) throw new Exception('Не удалось записать директорию:' . $dir);
|
||||
|
||||
$reader = new HtmlReader();
|
||||
$spreadsheet = $reader->loadFromString($html);
|
||||
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$writer->save($dir . '/invoice.xlsx');
|
||||
|
||||
// $reader = new XlsxReader();
|
||||
// $reader->setReadDataOnly(true);
|
||||
// $spreadsheet = $reader->load('../views/invoice/order/original.xlsx');
|
||||
|
||||
// $writer = new Html($spreadsheet);
|
||||
// $writer->save('hello world.html');
|
||||
}
|
||||
}
|
@@ -29,15 +29,25 @@ class Notification extends Document
|
||||
const SCENARIO_TRUSTED_CREATE = 'create';
|
||||
|
||||
/**
|
||||
* Тип уведомления: памятка
|
||||
* Уведомление: "памятка"
|
||||
*/
|
||||
const TYPE_NOTICE = 'notice';
|
||||
|
||||
/**
|
||||
* Тип уведомления: предупреждение
|
||||
* Уведомление: "предупреждение"
|
||||
*/
|
||||
const TYPE_WARNING = 'warning';
|
||||
|
||||
/**
|
||||
* Уведомление: "ошибка"
|
||||
*/
|
||||
const TYPE_ERROR = 'error';
|
||||
|
||||
/**
|
||||
* Уведомление для модераторов: "новый заказ"
|
||||
*/
|
||||
const TYPE_MODERATOR_ORDER_NEW = 'new order';
|
||||
|
||||
/**
|
||||
* Цель для отправки уведомления
|
||||
*
|
||||
@@ -129,7 +139,7 @@ class Notification extends Document
|
||||
*/
|
||||
public function write(): self|array|null
|
||||
{
|
||||
return $this::_write($this->text, $this->html, $this->account, $this->type);
|
||||
return $this::_write($this->text, $this->html, $this->account ?? null, $this->type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,14 +147,16 @@ class Notification extends Document
|
||||
*
|
||||
* @param string $html Содержимое уведомления (HTML или текст)
|
||||
* @param bool|string|null $html Содержимое уведомления (HTML или текст)
|
||||
* @param string $account Получатель уведомления
|
||||
* @param string $account Получатель уведомления (_key или "@...")
|
||||
* @param string $type Тип уведомления
|
||||
*
|
||||
* @todo Намного удобнее будет заменить _key на _id, чтобы из рёбер сразу получать аккаунт без лишних операций
|
||||
*/
|
||||
public static function _write(string $text, bool|string|null $html = false, string $account = null, string $type = self::TYPE_NOTICE): self|array|null
|
||||
public static function _write(string $text, bool|string|null $html = false, string $account = '', string $type = self::TYPE_NOTICE): self|array|null
|
||||
{
|
||||
// Инициализация
|
||||
$model = new self;
|
||||
$account or $account = yii::$app->user->identity->_key ?? throw new Exception('Не удалось инициализировать получателя');
|
||||
$receiver = Account::initAccount($account)->_key ?? $account ?? throw new Exception('Не удалось инициализировать получателя');
|
||||
|
||||
if ((bool) (int) $html) {
|
||||
// Получен текст в формете HTML-кода
|
||||
@@ -156,7 +168,7 @@ class Notification extends Document
|
||||
$text = htmlspecialchars(strip_tags($text ?? null));
|
||||
|
||||
$model->html = <<<HTML
|
||||
<p class="my-2 mx-3">$text</p>
|
||||
<p>$text</p>
|
||||
HTML;
|
||||
}
|
||||
|
||||
@@ -164,7 +176,7 @@ class Notification extends Document
|
||||
// Уведомление записано
|
||||
|
||||
// Инициализация получателей и создание ребра
|
||||
self::searchReceiverAndConnect($model, $account, $type);
|
||||
self::searchReceiverAndConnect($model, $receiver, $type);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -174,20 +186,20 @@ class Notification extends Document
|
||||
* Поиск получателя
|
||||
*
|
||||
* @param self $model Уведомление
|
||||
* @param string $text Необработанный текст
|
||||
* @param string $targets Необработанный текст с получателями
|
||||
* @param string $type Тип уведомления
|
||||
*/
|
||||
protected static function searchReceiverAndConnect(self $model, string $text, string $type = self::TYPE_NOTICE): AccountEdgeNotification|array|null
|
||||
protected static function searchReceiverAndConnect(self $model, string $targets, string $type = self::TYPE_NOTICE): AccountEdgeNotification|array|null
|
||||
{
|
||||
// Инициализация
|
||||
$return = [];
|
||||
|
||||
// Конвертация
|
||||
$accounts = array_map('trim', explode(self::$delimiter, $text));
|
||||
$accounts = array_map('trim', explode(self::$delimiter, $targets));
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
if (in_array('@all', $accounts, true)) {
|
||||
// Найден флаг обозначающий отправку всем пользователям
|
||||
// Всем пользователям
|
||||
|
||||
// Инициализация
|
||||
$return = [];
|
||||
@@ -195,11 +207,64 @@ class Notification extends Document
|
||||
foreach (Account::readAll() as $account) {
|
||||
// Перебор всех аккаунтов
|
||||
|
||||
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
|
||||
$return[] = AccountEdgeNotification::writeSafe($model->readId(), $account->readId(), $type);
|
||||
}
|
||||
} else if (
|
||||
in_array('@authorized', $accounts, true)
|
||||
|| in_array('@authorizeds', $accounts, true)
|
||||
|| in_array('@authorised', $accounts, true)
|
||||
|| in_array('@authoriseds', $accounts, true)
|
||||
|| in_array('@auth', $accounts, true)
|
||||
|| in_array('@autheds', $accounts, true)
|
||||
) {
|
||||
// Всем авторизованным (админам и модераторам)
|
||||
|
||||
// Инициализация
|
||||
$return = [];
|
||||
|
||||
foreach (Account::readAllAuthorizeds() as $account) {
|
||||
// Перебор всех аккаунтов
|
||||
|
||||
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
|
||||
$return[] = AccountEdgeNotification::writeSafe($model->readId(), $account->readId(), $type);
|
||||
}
|
||||
} else if (
|
||||
in_array('@administrator', $accounts, true)
|
||||
|| in_array('@administrators', $accounts, true)
|
||||
|| in_array('@admin', $accounts, true)
|
||||
|| in_array('@admins', $accounts, true)
|
||||
) {
|
||||
// Администраторам
|
||||
|
||||
// Инициализация
|
||||
$return = [];
|
||||
|
||||
foreach (Account::readAllAdministrators() as $account) {
|
||||
// Перебор всех аккаунтов
|
||||
|
||||
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
|
||||
$return[] = AccountEdgeNotification::writeSafe($model->readId(), $account->readId(), $type);
|
||||
}
|
||||
} else if (
|
||||
in_array('@moderator', $accounts, true)
|
||||
|| in_array('@moderators', $accounts, true)
|
||||
|| in_array('@moder', $accounts, true)
|
||||
|| in_array('@moders', $accounts, true)
|
||||
) {
|
||||
// Модераторам
|
||||
|
||||
// Инициализация
|
||||
$return = [];
|
||||
|
||||
foreach (Account::readAllModerators() as $account) {
|
||||
// Перебор всех аккаунтов
|
||||
|
||||
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
|
||||
$return[] = AccountEdgeNotification::writeSafe($model->readId(), $account->readId(), $type);
|
||||
}
|
||||
} else if (in_array('@test', $accounts, true)) {
|
||||
// Найден флаг обозначающий тестирование (отправка самому себе)
|
||||
// Тестирование (отправка самому себе)
|
||||
|
||||
$return[] = AccountEdgeNotification::writeSafe($model->readId(), yii::$app->user->id, $type);
|
||||
} else {
|
||||
@@ -216,4 +281,19 @@ class Notification extends Document
|
||||
|
||||
return $return ? $return : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Конвертация типа уведомления в версию для отображения
|
||||
*
|
||||
* @param string|null $type Тип уведомления
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function genTypeToRussian(string $type = null): string {
|
||||
return match($type ?? $this->type) {
|
||||
'notice' => 'Уведомление',
|
||||
'warning' => 'Предупреждение',
|
||||
'error' => 'Ошибка'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -5,10 +5,14 @@ declare(strict_types=1);
|
||||
namespace app\models;
|
||||
|
||||
use yii;
|
||||
use yii\web\User as Account;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
|
||||
use carono\exchange1c\controllers\ApiController;
|
||||
use carono\exchange1c\interfaces\DocumentInterface;
|
||||
|
||||
use app\models\connection\Dellin;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
@@ -17,14 +21,14 @@ use Exception;
|
||||
* @see Account Заказчик
|
||||
* @see Supply Поставки для заказа
|
||||
*/
|
||||
class Order extends Document
|
||||
class Order extends Document implements DocumentInterface
|
||||
{
|
||||
use SearchByEdge;
|
||||
|
||||
/**
|
||||
* Поставки для записи
|
||||
*/
|
||||
public array $supplies;
|
||||
public array|int $supplies;
|
||||
|
||||
/**
|
||||
* Имя коллекции
|
||||
@@ -42,7 +46,9 @@ class Order extends Document
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'stts'
|
||||
'ocid',
|
||||
'stts',
|
||||
'sync'
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -55,7 +61,9 @@ class Order extends Document
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'stts' => 'Статус'
|
||||
'ocid' => 'Идентификатор 1C',
|
||||
'stts' => 'Статус',
|
||||
'sync' => 'Статус синхронизации с 1C'
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -69,14 +77,14 @@ class Order extends Document
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
'stts',
|
||||
'string',
|
||||
'message' => '{attribute} должен быть строкой'
|
||||
'sync',
|
||||
'boolean',
|
||||
'message' => '{attribute} должен иметь логическое значение'
|
||||
],
|
||||
[
|
||||
'stts',
|
||||
'sync',
|
||||
'default',
|
||||
'value' => 'preparing'
|
||||
'value' => false
|
||||
]
|
||||
]
|
||||
);
|
||||
@@ -88,39 +96,35 @@ class Order extends Document
|
||||
public function connect(Account $account): ?AccountEdgeOrder
|
||||
{
|
||||
// Запись ребра: АККАУНТ -> ЗАКАЗ
|
||||
return AccountEdgeOrder::write($account->id, $this->readId(), 'current') ?? throw new Exception('Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ');
|
||||
return AccountEdgeOrder::write($account->readId(), $this->readId(), data: ['stts' => 'current']) ?? throw new Exception('Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись товара
|
||||
* Запись товара к заказу
|
||||
*
|
||||
* $supply = [ Supply $supply, int $amount = 1 ]
|
||||
*
|
||||
* @param Supply|array $supply Поставка
|
||||
* @param string $supply_id Идентификатор поставки
|
||||
* @param string $delivery_type Тип доставки
|
||||
* @param int $amount Количество
|
||||
* @param Account $trgt Заказчик
|
||||
*
|
||||
* @return int Количество записанных поставок
|
||||
*
|
||||
* @todo Создать параметр разделителя для администрации
|
||||
*/
|
||||
public function writeSupply(Supply|string|array $supply, Account $trgt = null): int
|
||||
public function writeSupply(string $supply_id, string $delivery_type, int $amount = 1, Account $trgt = null): int
|
||||
{
|
||||
// Инициализация
|
||||
$trgt ?? $trgt = yii::$app->user ?? throw new Exception('Не удалось инициализировать заказчика');
|
||||
|
||||
if ($supply instanceof Supply) {
|
||||
// Передана инстанция класса поставки или второй элемент массива не является числом
|
||||
|
||||
// Унификация входных данных
|
||||
$supply = [$supply->catn => 1];
|
||||
}
|
||||
$trgt ?? $trgt = yii::$app->user->identity ?? throw new Exception('Не удалось инициализировать заказчика');
|
||||
|
||||
// Проверка корзины
|
||||
if (is_null($this->_key)) {
|
||||
// Корзина не инициализирована
|
||||
|
||||
// Инициализация
|
||||
// Инициализация корзины
|
||||
if (!$this->save()) {
|
||||
// Инициализация заказа не удалась
|
||||
// Инициализация корзины (активного заказа) не удалась
|
||||
|
||||
throw new Exception('Ошибка при записи заказа в базу данных');
|
||||
}
|
||||
@@ -134,53 +138,58 @@ class Order extends Document
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$amount = 0;
|
||||
$amount_buffer = 0;
|
||||
|
||||
foreach (is_array($supply) ? $supply : [$supply => 1] as $supply_raw => $amount_raw) {
|
||||
// Перебор поставок
|
||||
// Обработка поставок
|
||||
for ($i = 0; $i < $amount; $i++) {
|
||||
// Создание рёбер соразмерно запросу (добавление нескольких продуктов в корзину)
|
||||
|
||||
for ($i = 0; $i < $amount_raw; $i++) {
|
||||
// Создание рёбер соразмерно запросу (добавление нескольких продуктов в корзину)
|
||||
// Запись ребра: ЗАКАЗ -> ПОСТАВКА
|
||||
if (!$supply_model = Supply::searchById($supply_id) or !$order_edge_supply = OrderEdgeSupply::write($this->readId(), $supply_model->readId(), 'write')) {
|
||||
// Поставка не найдена или запись ребра не удалась
|
||||
|
||||
// Запись ребра: ЗАКАЗ -> ПОСТАВКА
|
||||
if (!$supply_model = Supply::searchByCatn($supply_raw) or !OrderEdgeSupply::write($this->readId(), $supply_model->readId(), 'write')) {
|
||||
// Поставка не найдена или запись ребра не удалась
|
||||
continue;
|
||||
} else {
|
||||
// Ребро создано (товар подключен к заказу)
|
||||
|
||||
continue;
|
||||
} else {
|
||||
// Ребро создано (товар подключен к заказу)
|
||||
// Обновление счётчика добавленных товаров
|
||||
$amount_buffer++;
|
||||
|
||||
// Постинкрементация счётчика добавленных товаров
|
||||
$amount++;
|
||||
// Запись типа доставки
|
||||
$order_edge_supply->dlvr = [
|
||||
'type' => $delivery_type
|
||||
];
|
||||
$order_edge_supply->update();
|
||||
|
||||
// Запись в журнал
|
||||
$this->journal('write', ['target' => $supply_model->readId()]);
|
||||
}
|
||||
// Запись в журнал
|
||||
$this->journal('write', ['target' => $supply_model->readId()]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($amount === 0) {
|
||||
if ($amount_buffer === 0) {
|
||||
// Отправка уведомления
|
||||
self::notification('Неудачная попытка добавить товар в корзину');
|
||||
} else if ($amount === 1) {
|
||||
} else if ($amount_buffer === 1) {
|
||||
// Отправка уведомления
|
||||
self::notification('Товар ' . $supply_model->catn . ' добавлен в корзину');
|
||||
} else {
|
||||
// Отправка уведомления
|
||||
self::notification('Добавлено ' . $amount . ' товаров в корзину');
|
||||
self::notification('Добавлено ' . $amount_buffer . ' товаров в корзину');
|
||||
}
|
||||
|
||||
return $amount;
|
||||
return $amount_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление поставки
|
||||
*
|
||||
* @param Supply|string|array $supply Товары
|
||||
* @param Supply|array $supply Товары
|
||||
*
|
||||
* @return int Количество удалённых рёбер
|
||||
*
|
||||
* @todo Доделать
|
||||
*/
|
||||
public function deleteSupply(Supply|string|array $supply): int
|
||||
public function deleteSupply(Supply|array $supply): int
|
||||
{
|
||||
// Инициализация
|
||||
$amount = 0;
|
||||
@@ -189,37 +198,61 @@ class Order extends Document
|
||||
// Передана инстанция класса поставки или второй элемент массива не является числом
|
||||
|
||||
// Унификация входных данных
|
||||
$supply = [$supply->catn => 1];
|
||||
$supply = [
|
||||
$supply->catn => [
|
||||
'auto' => 1
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
foreach (is_array($supply) ? $supply : [$supply => 1] as $catn => $amount_raw) {
|
||||
// Перебор товаров
|
||||
foreach ($supply as $catn => $data) {
|
||||
// Перебор целей
|
||||
|
||||
if ($supply = Supply::searchByCatn($catn)) {
|
||||
foreach (OrderEdgeSupply::searchByVertex($this->readId(), $supply->readId(), limit: $amount_raw) as $edge) {
|
||||
// Перебор рёбер до продукта (если товаров в заказе несколько)
|
||||
var_dump('ок');
|
||||
|
||||
// Удаление
|
||||
$edge->delete();
|
||||
foreach ($data as $type => $delete_amount) {
|
||||
// Перебор данных цели
|
||||
|
||||
// Запись в журнал
|
||||
$this->journal('delete', ['target' => $supply->readId()]);
|
||||
var_dump('да');
|
||||
if ($supply = Supply::searchByCatn($catn)) {
|
||||
// Поставка найдена
|
||||
|
||||
// Постинкрементация счётчика удалённых рёбер
|
||||
$amount++;
|
||||
$edges = OrderEdgeSupply::searchByVertex($this->readId(), $supply->readId(), limit: $delete_amount, filter: ['order_edge_supply.dlvr.type == \'' . $type . '\'']);
|
||||
|
||||
for (; count($edges) > $amount; $amount++) {
|
||||
|
||||
var_dump(count($edges), $amount, $delete_amount);
|
||||
var_dump(PHP_EOL);
|
||||
|
||||
// Удаление из базы данных
|
||||
$edges[$amount]->delete();
|
||||
|
||||
// Запись в журнал
|
||||
$this->journal(
|
||||
'delete',
|
||||
[
|
||||
'target' => [
|
||||
$supply->readId() => $type
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Генерация вставки текста с типом доставки
|
||||
$type = Supply::DeliveryToRussian($type);
|
||||
|
||||
if ($amount === 0) {
|
||||
// Отправка уведомления
|
||||
self::notification('Неудачная попытка удалить товар из корзины');
|
||||
self::notification('Не удалось удалить товар из корзины');
|
||||
} else if ($amount === 1) {
|
||||
// Отправка уведомления
|
||||
self::notification('Товар ' . $supply->catn . ' удалён из корзины');
|
||||
self::notification("Товар $supply->catn c $type удалён из корзины");
|
||||
} else {
|
||||
// Отправка уведомления
|
||||
self::notification('Удалено ' . $amount . ' товаров из корзины');
|
||||
self::notification("Удалено $amount товаров из корзины");
|
||||
}
|
||||
|
||||
return $amount;
|
||||
@@ -227,58 +260,183 @@ class Order extends Document
|
||||
|
||||
/**
|
||||
* Поиск заказа
|
||||
*
|
||||
* @todo Привести в порядок
|
||||
*/
|
||||
public static function search(Account $account = null, string $type = 'current', int $limit = 1, int $page = 1, string $select = null): self|array|null
|
||||
{
|
||||
// Инициализация
|
||||
$account or $account = yii::$app->user ?? throw new Exception('Не удалось инициализировать пользователя');
|
||||
public static function searchSmart(
|
||||
Account|string $account = null,
|
||||
string $stts = 'current',
|
||||
string|null $search = null,
|
||||
int $limit = 1,
|
||||
int $page = 1,
|
||||
string|null $select = null,
|
||||
bool $supplies = false,
|
||||
int|null $from = null,
|
||||
int|null $to = null,
|
||||
bool $count = false,
|
||||
bool $debug = false
|
||||
): int|array|null {
|
||||
// Инициализация аккаунта
|
||||
if (empty($account)) {
|
||||
// Не получен аккаунт
|
||||
|
||||
// Генерация сдвига по запрашиваемым данным (пагинация)
|
||||
$offset = $limit * ($page - 1);
|
||||
|
||||
if (strcasecmp($type, 'all') !== 0) {
|
||||
// Если не указан параметр поиска всех заказов
|
||||
|
||||
$where_type = [
|
||||
'account_edge_order.type' => $type
|
||||
$subquery_where = [
|
||||
[
|
||||
'account._id' => Account::initAccount()->readId()
|
||||
]
|
||||
];
|
||||
} else if ($account instanceof Account) {
|
||||
// Получен аккаунт
|
||||
|
||||
if (Account::isMinimalAuthorized(Account::initAccount())) {
|
||||
$subquery_where = [
|
||||
[
|
||||
'account._id' => $account->readId()
|
||||
]
|
||||
];
|
||||
} else {
|
||||
throw new Exception('У вас нет прав на обработку другого пользователя');
|
||||
}
|
||||
} else if (str_contains($account, '@all')) {
|
||||
// Получен запрос на обработку всех аккаунтов
|
||||
|
||||
$subquery_where = [];
|
||||
} else {
|
||||
$where_type = [];
|
||||
throw new Exception('Не удалось инициализировать пользователя');
|
||||
}
|
||||
|
||||
$return = self::searchByEdge(
|
||||
// Инициализация типа заказа
|
||||
if (strcasecmp($stts, '@all') !== 0) {
|
||||
// Если не указан поиск всех заказов
|
||||
|
||||
$subquery_where[] = [
|
||||
'account_edge_order.stts' => $stts
|
||||
];
|
||||
}
|
||||
|
||||
// Инициализация сдвига по запрашиваемым данным (пагинация)
|
||||
$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";
|
||||
}
|
||||
|
||||
// Поиск заказов в базе данных
|
||||
$orders = self::searchByEdge(
|
||||
from: 'account',
|
||||
to: 'order',
|
||||
subquery_where: [
|
||||
[
|
||||
'account._id' => $account->id
|
||||
],
|
||||
$where_type
|
||||
],
|
||||
foreach: ['edge' => 'account_edge_order'],
|
||||
where: 'edge._to == order._id',
|
||||
subquery_where: $subquery_where,
|
||||
foreach: $foreach,
|
||||
where: $where,
|
||||
limit: $limit,
|
||||
offset: $offset,
|
||||
sort: ['DESC'],
|
||||
select: $select,
|
||||
direction: 'INBOUND'
|
||||
direction: 'INBOUND',
|
||||
count: !$supplies && $count,
|
||||
debug: $debug
|
||||
);
|
||||
|
||||
return $limit === 1 ? $return[0] ?? null : $return;
|
||||
if ($debug) {
|
||||
var_dump($orders);
|
||||
die;
|
||||
}
|
||||
|
||||
if (!$supplies && $count) {
|
||||
// Запрошен подсчет заказов
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
// Инициализация буфера возврата
|
||||
$return = [];
|
||||
|
||||
// Инициализация архитектуры буфера вывода
|
||||
foreach ($orders ?? [null] as $key => $order) {
|
||||
// Перебор заказов
|
||||
|
||||
// Запись в буфер возврата
|
||||
$return[$key]['order'] = $order;
|
||||
}
|
||||
|
||||
if ($supplies) {
|
||||
// Запрошен поиск поставок
|
||||
|
||||
foreach ($return as $key => &$container) {
|
||||
// Перебор заказов
|
||||
|
||||
|
||||
if ($container['order'] instanceof Order) {
|
||||
// Инстанция заказа
|
||||
|
||||
// Инициализация заказа
|
||||
$order = $container['order'];
|
||||
} else {
|
||||
// Массив с заказом (подразумевается)
|
||||
|
||||
// Инициализация настроек
|
||||
$config = $container['order'];
|
||||
unset($config['_id'], $config['_rev'], $config['_id']);
|
||||
|
||||
// Инициализация заказа
|
||||
$order = new Order($config);
|
||||
}
|
||||
|
||||
// Чтение полного содержания
|
||||
$return[$key]['supplies'] = $order->supplies($limit, $page, $search, count: $count);
|
||||
|
||||
if ($count) {
|
||||
// Запрошен подсчет поставок (переделать под подсчёт)
|
||||
|
||||
return $return[$key]['supplies'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск содержимого заказа
|
||||
* Поиск содержимых поставок заказа
|
||||
*
|
||||
* @todo В будущем возможно заказ не только поставок реализовать
|
||||
* Переписать реестр и проверку на дубликаты, не понимаю как они работают
|
||||
*/
|
||||
public function content(int $limit = 1, int $page = 1): Supply|array|null
|
||||
public function supplies(int $limit = 1, int $page = 1, string|null $search = null, bool $count = false): Supply|int|array|null
|
||||
{
|
||||
// Инициализация аккаунта
|
||||
$account = Account::initAccount();
|
||||
|
||||
// Генерация сдвига по запрашиваемым данным (пагинация)
|
||||
$offset = $limit * ($page - 1);
|
||||
|
||||
// Поиск рёбер: ЗАКАЗ -> ПОСТАВКА
|
||||
$supplies = Supply::searchByEdge(
|
||||
if (!empty($search)) {
|
||||
// Передан поиск по продуктам в заказах
|
||||
|
||||
// Запись ограничения по максимальному значению
|
||||
$limit = 9999;
|
||||
}
|
||||
|
||||
// Поиск по рёбрам: ЗАКАЗ -> ПОСТАВКА
|
||||
$connections = Supply::searchByEdge(
|
||||
from: 'order',
|
||||
to: 'supply',
|
||||
edge: 'order_edge_supply',
|
||||
@@ -287,77 +445,307 @@ class Order extends Document
|
||||
'order._id' => $this->readId()
|
||||
]
|
||||
],
|
||||
foreach: ['edge' => 'order_edge_supply'],
|
||||
where: 'edge._to == supply._id',
|
||||
where: 'order_edge_supply != []',
|
||||
limit: $limit,
|
||||
offset: $offset,
|
||||
direction: 'INBOUND'
|
||||
filterStart: ['catn' => $search],
|
||||
direction: 'INBOUND',
|
||||
select: '{order_edge_supply}',
|
||||
count: $count
|
||||
);
|
||||
|
||||
// Инициализация реестра дубликатов
|
||||
$registry = [];
|
||||
if ($count) {
|
||||
// Подсчёт запрошен
|
||||
|
||||
// Подсчёт и перестройка массива для очистки от дубликатов
|
||||
foreach ($supplies as $key => &$supply) {
|
||||
// Перебор поставок
|
||||
|
||||
if (in_array($supply->catn, $registry)) {
|
||||
// Если данная поставка найдена в реестре
|
||||
|
||||
// Удаление
|
||||
unset($supplies[$key]);
|
||||
|
||||
// Пропуск итерации
|
||||
continue;
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$amount = 0;
|
||||
|
||||
// Повторный перебор для поиска дубликатов
|
||||
foreach ($supplies as &$supply4check) {
|
||||
if ($supply == $supply4check) {
|
||||
// Найден дубликат
|
||||
|
||||
// Постинкрементация счётчика
|
||||
$amount++;
|
||||
|
||||
// Запись в реестр
|
||||
$registry[] = $supply4check->catn;
|
||||
}
|
||||
}
|
||||
|
||||
// Запись количества для заказа
|
||||
$supply->amnt = $amount;
|
||||
return $connections;
|
||||
}
|
||||
|
||||
// Поиск стоимости для каждой поставки
|
||||
foreach ($supplies as $key => &$supply) {
|
||||
// Перебор поставок
|
||||
// Инициализация реестра дубликатов
|
||||
$supplies = [];
|
||||
|
||||
// Чтение стоимости
|
||||
$cost = $supply->readCost();
|
||||
foreach ($connections as $key => &$connection) {
|
||||
// Перебор объектов для заказа
|
||||
|
||||
if ($cost < 1) {
|
||||
// Если стоимость равна нулю (явная ошибка)
|
||||
foreach ($connection['order_edge_supply'] as $edge) {
|
||||
// Перебор связанных поставок
|
||||
|
||||
// Удаление из базы данных
|
||||
$this->deleteSupply($supply->readId());
|
||||
// Инициализация связанной с заказом поставки
|
||||
$supply = Supply::searchById($edge['_to']);
|
||||
|
||||
// Удаление из списка
|
||||
unset($supplies[$key]);
|
||||
// Инициализация поставщика в буфере
|
||||
if (empty($supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']])) $supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']] = [
|
||||
'supply' => $supply,
|
||||
'account' => Account::searchBySupplyId($supply->readId()),
|
||||
'product' => Product::searchBySupplyId($supply->readId()),
|
||||
'currency' => 'руб',
|
||||
'amount' => 1
|
||||
];
|
||||
else ++$supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']]['amount'];
|
||||
|
||||
// Пропуск итерации
|
||||
continue;
|
||||
// Инициализация буфера с обрабатываемой поставкой
|
||||
$buffer = &$supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']];
|
||||
|
||||
// Запись ребра
|
||||
$buffer['edge'] = $edge;
|
||||
|
||||
if (empty($buffer['supply']->cost) || $buffer['supply']->cost < 1) {
|
||||
// Если стоимость не найдена или равна нулю (явная ошибка)
|
||||
|
||||
// Удаление из базы данных
|
||||
$this->deleteSupply($buffer['supply']);
|
||||
|
||||
// Инициализация стоимости товара для уведомления (чтобы там не было NULL)
|
||||
$cost = $buffer['supply']->cost ?? 0;
|
||||
|
||||
// Отправка уведомлений покупателю
|
||||
Notification::_write("Стоимость товара $supply->catn равна $cost", account: $account->_key, type: Notification::TYPE_ERROR);
|
||||
Notification::_write("Товар $supply->catn удалён", account: $account->_key, type: Notification::TYPE_ERROR);
|
||||
|
||||
// Отправка уведомления поставщику
|
||||
|
||||
// Отправка уведомления модератору
|
||||
|
||||
// Удаление из списка
|
||||
unset($connections[$key]);
|
||||
|
||||
// Выход из цикла
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
// Инициализация данных геолокации
|
||||
try {
|
||||
$from = (int) $buffer['account']['opts']['delivery_from_terminal'] ?? empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default;
|
||||
} catch (Exception $e) {
|
||||
$from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default;
|
||||
}
|
||||
|
||||
try {
|
||||
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
|
||||
} catch (Exception $e) {
|
||||
$to = 36;
|
||||
}
|
||||
|
||||
if (($buffer_connection = $buffer['product']['bffr']["$from-$to-" . $edge['dlvr']['type']] ?? false) && time() < $buffer_connection['expires']) {
|
||||
// Найдены данные доставки в буфере и их срок хранения не превышен, информация актуальна
|
||||
|
||||
// Запись в буфер вывода
|
||||
$buffer['delivery'] = $buffer_connection['data'];
|
||||
} else {
|
||||
// Инициализация инстанции продукта в базе данных (реинициализация под ActiveRecord)
|
||||
|
||||
$product = Product::searchByCatnAndProd($buffer['product']['catn'], $buffer['product']['prod']);
|
||||
|
||||
// Инициализация доставки Dellin (автоматическая)
|
||||
$product->bffr = ($product->bffr ?? []) + [
|
||||
"$from-$to-" . $edge['dlvr']['type'] => [
|
||||
'data' => $buffer['delivery'] = Dellin::calcDeliveryAdvanced(
|
||||
$from,
|
||||
$to,
|
||||
(int) ($buffer['product']['wght'] ?? 0),
|
||||
(int) ($buffer['product']['dmns']['x'] ?? 0),
|
||||
(int) ($buffer['product']['dmns']['y'] ?? 0),
|
||||
(int) ($buffer['product']['dmns']['z'] ?? 0),
|
||||
avia: $edge['dlvr']['type'] === 'avia'
|
||||
),
|
||||
'expires' => time() + 86400
|
||||
]
|
||||
];
|
||||
|
||||
// Отправка в базу данных
|
||||
$product->update();
|
||||
}
|
||||
|
||||
|
||||
// Запись цены (цена поставки + цена доставки + наша наценка)
|
||||
$buffer['cost'] = ($supply->cost ?? $supply->onec['Цены']['Цена']['ЦенаЗаЕдиницу'] ?? throw new exception('Не найдена цена товара')) + ($buffer['delivery']['price']['all'] ?? $buffer['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0) ?? 0;
|
||||
} catch (Exception $e) {
|
||||
$buffer['delivery'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Запись цены
|
||||
$supply->cost = $cost['ЦенаЗаЕдиницу'] . ' ' . $cost['Валюта'];
|
||||
}
|
||||
|
||||
return $supplies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка на то, что все поставки подтверждены
|
||||
*
|
||||
* @param array $order_edge_supply Поставки
|
||||
*
|
||||
* @return bool Статус подтверждения всех поставок (true если все и false если хотя бы одна из них не подтверждена)
|
||||
*/
|
||||
public static function checkSuppliesStts(array $order_edge_supply): bool
|
||||
{
|
||||
if (isset($order_edge_supply['_key'])) {
|
||||
// Получено ребро: ЗАКАЗ -> ПОСТАВКА
|
||||
|
||||
if (empty($order_edge_supply['stts']) || $order_edge_supply['stts'] === 'processing' || $order_edge_supply['stts'] === 'requested') {
|
||||
// Найдена неподтверждённая поставка
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (isset($order_edge_supply[0]['_key'])) {
|
||||
// Получен массив (подразумевается, что с рёбрами: ЗАКАЗ -> ПОСТАВКА)
|
||||
|
||||
foreach ($order_edge_supply as $edge) {
|
||||
// Перебор поставок
|
||||
|
||||
if (empty($edge['stts']) || $edge['stts'] === 'processing' || $edge['stts'] === 'requested') {
|
||||
// Найдена неподтверждённая поставка
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DocumentInterface[]
|
||||
*/
|
||||
public static function findDocuments1c(): ?array
|
||||
{
|
||||
// yii::$app->on(ApiController::EVENT_AFTER_EXPORT_ORDERS, self::afterExport1c());
|
||||
|
||||
$orders = self::searchByEdge(
|
||||
from: 'account',
|
||||
to: 'order',
|
||||
edge: 'account_edge_order',
|
||||
direction: 'INBOUND',
|
||||
subquery_where: 'account_edge_order.type == "processed"',
|
||||
where: ['sync' => false]
|
||||
);
|
||||
|
||||
foreach ($orders as &$order) {
|
||||
// Перебор заказов
|
||||
|
||||
// Запись о том, что синхронизация проведена
|
||||
$order->sync = true;
|
||||
|
||||
$order->update();
|
||||
}
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OfferInterface[]
|
||||
*/
|
||||
public function getOffers1c(): mixed
|
||||
{
|
||||
// Инициализация
|
||||
$supplies = [];
|
||||
|
||||
foreach ($this->jrnl as $key => $jrnl) {
|
||||
// Перебор журнала
|
||||
|
||||
// if (isset($supplies[$key]) && $supplies[$key]['id'] !== $jrnl['target']) {
|
||||
// // Запись уже существует и идентификаторы не совпадают
|
||||
|
||||
// $key .= '_du'
|
||||
|
||||
// // Реинициализация
|
||||
// $supplies[$key]['id'] = $jrnl['target'];
|
||||
// } else {
|
||||
// // Инициализация
|
||||
// }
|
||||
|
||||
|
||||
if (($jrnl['action'] ?? null) === 'write') {
|
||||
// Найдено событие записи товара к заказу
|
||||
|
||||
$supplies[$key]['id'] = $jrnl['target'];
|
||||
|
||||
$supplies[$key]['amount'] ?? $supplies[$key]['amount'] = 0;
|
||||
|
||||
++$supplies[$key]['amount'];
|
||||
} else if (($jrnl['action'] ?? null) === 'delete') {
|
||||
// Найдено событие удаления товара из заказа
|
||||
|
||||
$supplies[$key]['id'] = $jrnl['target'];
|
||||
|
||||
$supplies[$key]['amount'] ?? $supplies[$key]['amount'] = 0;
|
||||
|
||||
--$supplies[$key]['amount'];
|
||||
}
|
||||
}
|
||||
|
||||
// file_put_contents('supplies.txt', print_r($supplies, true));
|
||||
|
||||
if (count($supplies) > 0) {
|
||||
// Поставки были записаны
|
||||
|
||||
// Инициализация
|
||||
$supplies_buffer = [];
|
||||
|
||||
foreach ($supplies as $id => $supply) {
|
||||
// Перебор поставок
|
||||
|
||||
if ($supply['amount'] < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($response = Supply::searchById($supply['id'])) {
|
||||
// Поставка найдена в базе данных
|
||||
|
||||
$supplies_buffer[] = $response;
|
||||
}
|
||||
}
|
||||
|
||||
return $supplies_buffer;
|
||||
}
|
||||
// file_put_contents('AAAAAAAAAAAAAAAAAAA.txt', print_r(1, true));
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Неизвестно для чего
|
||||
*/
|
||||
public function getRequisites1c(): void
|
||||
{
|
||||
// return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить контрагента (пользователя от лица которого происходит операция)
|
||||
*
|
||||
* @return PartnerInterface
|
||||
*/
|
||||
public function getPartner1c(): Account
|
||||
{
|
||||
return yii::$app->user->identity ?? throw new Exception('Не удалось идентифицировать пользователя');
|
||||
}
|
||||
|
||||
public function getExportFields1c($context = null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращаем имя поля в базе данных, в котором хранится ID из 1с
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getIdFieldName1c()
|
||||
{
|
||||
return 'ocid';
|
||||
}
|
||||
|
||||
public function setRaw1cData($cml, $object): void
|
||||
{
|
||||
}
|
||||
|
||||
protected static function afterExport1c(): void
|
||||
{
|
||||
file_put_contents('afterExport1c.txt', print_r(1, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправка уведомления
|
||||
*/
|
||||
@@ -366,4 +754,58 @@ class Order extends Document
|
||||
// Отправка
|
||||
return Notification::_write($text, type: $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Посчитать количство заказов или количество поставок в заказах
|
||||
*
|
||||
* @param int $limit Ограничение
|
||||
* @param bool $supplies Считать поставки в активном заказе (иначе - заказы)
|
||||
*
|
||||
* @return int Количество
|
||||
*/
|
||||
public static function count(int $limit = 500, bool $supplies = false): int
|
||||
{
|
||||
return (int) self::searchSmart(supplies: $supplies, stts: $supplies ? 'current' : '@all', limit: $limit, count: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация ярлыка на русском языке для статуса заказа
|
||||
*
|
||||
* @param string|null $status Статус заказа
|
||||
*
|
||||
* @return string Ярлык статуса на русском языке
|
||||
*/
|
||||
public static function statusToRussian(?string $status = 'processing'): string
|
||||
{
|
||||
return match ($status) {
|
||||
'processing' => 'Обрабатывается',
|
||||
'requested' => 'Запрошен',
|
||||
'accepted' => 'Ожидается отправка',
|
||||
'going' => 'Доставляется',
|
||||
'ready' => 'Готов к выдаче',
|
||||
'completed' => 'Завершен',
|
||||
'reserved' => 'Резервирован',
|
||||
default => 'Обрабатывается'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация списка статусов на русском языке
|
||||
*
|
||||
* @param string|null $active Активный статус, который выведется первым в списке
|
||||
*
|
||||
* @return string Лист статусов на русском языке
|
||||
*/
|
||||
public static function statusListInRussian(?string $active = 'processing'): array
|
||||
{
|
||||
return [(empty($active) ? 'processing' : $active) => static::statusToRussian($active)] + [
|
||||
'processing' => 'Обрабатывается',
|
||||
'requested' => 'Запрошен',
|
||||
'accepted' => 'Ожидается отправка',
|
||||
'going' => 'Доставляется',
|
||||
'ready' => 'Готов к выдаче',
|
||||
'completed' => 'Завершен',
|
||||
'reserved' => 'Резервирован'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,164 @@ namespace app\models;
|
||||
|
||||
class OrderEdgeSupply extends Edge
|
||||
{
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'order_edge_supply';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'comm',
|
||||
'cost',
|
||||
'time',
|
||||
'stts',
|
||||
'dlvr'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'comm' => 'Комментарий',
|
||||
'cost' => 'Цена',
|
||||
'time' => 'Время',
|
||||
'stts' => 'Статус',
|
||||
'dlvr' => 'Доставка'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
'comm',
|
||||
'string',
|
||||
'length' => [0, 300],
|
||||
'message' => '{attribute} должен быть строкой от 0 до 300 символов'
|
||||
],
|
||||
[
|
||||
'stts',
|
||||
'string',
|
||||
'length' => [0, 15],
|
||||
'message' => '{attribute} должен быть строкой от 0 до 15 символов'
|
||||
],
|
||||
[
|
||||
'cost',
|
||||
'integer',
|
||||
'message' => '{attribute} должна быть числом'
|
||||
],
|
||||
[
|
||||
'time',
|
||||
'integer',
|
||||
'message' => '{attribute} должно быть числом'
|
||||
],
|
||||
[
|
||||
'dlvr',
|
||||
'arrayValidator',
|
||||
'message' => '{attribute} должен быть массивом'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск поставки по артикулу
|
||||
*
|
||||
* @param string $catn Артикул
|
||||
* @param Order $order Заказ
|
||||
* @param int $limit Максимальное количество
|
||||
*
|
||||
* @return array Поставки
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static function searchBySupplyCatn(string $catn, Order $order = null, int $limit = 10): array
|
||||
{
|
||||
if ($supply = Supply::searchByCatn($catn, 1)) {
|
||||
// Поставка найдена
|
||||
|
||||
if (isset($order)) {
|
||||
// Поиск только по определённому заказу
|
||||
|
||||
return self::find()->where(['_from' => $order->readId(), '_to' => $supply->readId()])->limit($limit)->all();
|
||||
}
|
||||
|
||||
return self::find()->where(['_to' => $supply->readId()])->limit($limit)->all();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Поиск по данным поставки
|
||||
*
|
||||
* @param string $catn Артикул
|
||||
* @param Order $order Заказ
|
||||
* @param int $limit Максимальное количество
|
||||
*
|
||||
* @return array Поставки
|
||||
*/
|
||||
public static function searchBySupplyData(string $catn, string $prod, ?string $delivery = null, ?Order $order = null, int $limit = 10): array
|
||||
{
|
||||
if ($supply = Supply::searchByCatnAndProd($catn, $prod)) {
|
||||
// Поставка найдена
|
||||
|
||||
if (isset($order)) {
|
||||
// Поиск только по определённому заказу
|
||||
|
||||
if (isset($delivery)) {
|
||||
// Поиск только по определённым типам доставки
|
||||
|
||||
return self::find()->where(['_from' => $order->readId(), '_to' => $supply->readId(), 'order_edge_supply.dlvr.type' => $delivery])->limit($limit)->all();
|
||||
}
|
||||
|
||||
return self::find()->where(['_from' => $order->readId(), '_to' => $supply->readId()])->limit($limit)->all();
|
||||
}
|
||||
|
||||
return self::find()->where(['_to' => $supply->readId()])->limit($limit)->all();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация ярлыка на русском языке для статуса заказа
|
||||
*
|
||||
* @param string|null $status Статус заказа
|
||||
*
|
||||
* @return string Ярлык статуса на русском языке
|
||||
*/
|
||||
public static function statusToRussian(?string $status = 'processing'): string
|
||||
{
|
||||
return match ($status) {
|
||||
'requested' => 'Запрошен',
|
||||
'accepted' => 'Ожидается отправка',
|
||||
'going' => 'Доставляется',
|
||||
'completed' => 'Завершен',
|
||||
'processing' => 'Обрабатывается',
|
||||
default => 'Обрабатывается'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -4,12 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use yii;
|
||||
use yii\web\UploadedFile;
|
||||
use yii\imagine\Image;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
|
||||
use moonland\phpexcel\Excel;
|
||||
|
||||
/**
|
||||
@@ -43,20 +41,24 @@ class Product extends Document
|
||||
const SCENARIO_WRITE = 'write';
|
||||
|
||||
/**
|
||||
* Файл .excel для импорта товаров
|
||||
* Аккаунт
|
||||
*
|
||||
* Используется для управления администратором от лица пользователя
|
||||
*/
|
||||
public Excel|string|array|null $file_excel = null;
|
||||
public Account|string|null $account = null;
|
||||
|
||||
/**
|
||||
* Файл .excel для импорта товаров
|
||||
*
|
||||
* Универсальный, когда неизвестно на какую позицию загружать каталог
|
||||
*/
|
||||
public Excel|UploadedFile|string|null $file_excel = null;
|
||||
|
||||
/**
|
||||
* Изображение для импорта
|
||||
*/
|
||||
public UploadedFile|string|array|null $file_image = null;
|
||||
|
||||
/**
|
||||
* Группа в которой состоит товар
|
||||
*/
|
||||
public ProductGroup|null $group = null;
|
||||
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
@@ -75,12 +77,15 @@ class Product extends Document
|
||||
[
|
||||
'catn',
|
||||
'name',
|
||||
'desc',
|
||||
'ocid',
|
||||
// В библеотеке есть баг на название DESC (неизвестно в моей или нет)
|
||||
'dscr',
|
||||
'prod',
|
||||
'dmns',
|
||||
'wght',
|
||||
'imgs',
|
||||
'time',
|
||||
'oemn',
|
||||
'cost'
|
||||
'bffr',
|
||||
'stts'
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -95,21 +100,25 @@ class Product extends Document
|
||||
[
|
||||
'catn' => 'Каталожный номер (catn)',
|
||||
'name' => 'Название (name)',
|
||||
'desc' => 'Описание (desc)',
|
||||
'ocid' => 'Идентификатор 1C (ocid)',
|
||||
'dscr' => 'Описание (dscr)',
|
||||
'prod' => 'Производитель (prod)',
|
||||
'dmns' => 'Габариты (dmns)',
|
||||
'wght' => 'Вес (wght)',
|
||||
'imgs' => 'Изображения (imgs)',
|
||||
'time' => 'Срок доставки (time)',
|
||||
'oemn' => 'OEM номера (oemn)',
|
||||
'cost' => 'Стоимость (cost)',
|
||||
'bffr' => 'Буфер',
|
||||
'stts' => 'Статус',
|
||||
'file_excel' => 'Документ (file_excel)',
|
||||
'file_image' => 'Изображение (file_image)',
|
||||
'group' => 'Группа (group)'
|
||||
'account' => 'Аккаунт'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*
|
||||
* @todo Правило для всех трёх габаритов
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -130,25 +139,48 @@ class Product extends Document
|
||||
],
|
||||
[
|
||||
[
|
||||
'oemn',
|
||||
'imgs'
|
||||
'prod',
|
||||
'name'
|
||||
],
|
||||
'arrayValidator',
|
||||
'message' => '{attribute} должен быть массивом.'
|
||||
'string',
|
||||
'length' => [2, 80],
|
||||
'message' => '{attribute} должен быть строкой от 3 до 80 символов'
|
||||
],
|
||||
[
|
||||
'file_excel',
|
||||
'required',
|
||||
'message' => 'Заполните поля: {attribute}',
|
||||
'on' => self::SCENARIO_IMPORT_EXCEL
|
||||
'imgs',
|
||||
'arrayValidator',
|
||||
'message' => '{attribute} должен быть массивом'
|
||||
],
|
||||
[
|
||||
'dscr',
|
||||
'string',
|
||||
'length' => [3, 256],
|
||||
'message' => '{attribute} должен быть строкой от 3 до 256 символов'
|
||||
],
|
||||
[
|
||||
'dmns',
|
||||
'arrayWithNumbersValidator',
|
||||
'message' => '{attribute} должен быть массивом и хранить циферные значения'
|
||||
],
|
||||
[
|
||||
'wght',
|
||||
'integer',
|
||||
'min' => 0,
|
||||
'max' => 30000,
|
||||
'message' => '{attribute} должен иметь значение от 0 до 30000'
|
||||
],
|
||||
[
|
||||
'stts',
|
||||
'string',
|
||||
'length' => [4, 20],
|
||||
'message' => '{attribute} должен быть строкой от 4 до 20 символов'
|
||||
],
|
||||
[
|
||||
'file_excel',
|
||||
'file',
|
||||
'skipOnEmpty' => false,
|
||||
'skipOnEmpty' => true,
|
||||
'extensions' => 'xlsx',
|
||||
'checkExtensionByMimeType' => false,
|
||||
'maxFiles' => 5,
|
||||
'maxSize' => 1024 * 1024 * 30,
|
||||
'wrongExtension' => 'Разрешены только документы в формате: ".xlsx"',
|
||||
'message' => 'Проблема при чтении документа',
|
||||
@@ -164,7 +196,7 @@ class Product extends Document
|
||||
'file_image',
|
||||
'file',
|
||||
'skipOnEmpty' => false,
|
||||
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
|
||||
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp', 'jfif'],
|
||||
'checkExtensionByMimeType' => true,
|
||||
'maxFiles' => 10,
|
||||
'maxSize' => 1024 * 1024 * 30,
|
||||
@@ -177,88 +209,45 @@ class Product extends Document
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация продукта
|
||||
* Перед сохранением
|
||||
*
|
||||
* @param string $catn Артикул, каталожный номер
|
||||
* @todo Подождать обновление от ебаного Yii2 и добавить
|
||||
* проверку типов передаваемых параметров
|
||||
*/
|
||||
public static function initEmpty(string $catn): self|array
|
||||
public function beforeSave($create): bool
|
||||
{
|
||||
$oemn = self::searchOemn($catn);
|
||||
if (parent::beforeSave($create)) {
|
||||
// Пройдена родительская проверка
|
||||
|
||||
if (count($oemn) === 1) {
|
||||
// Передан только один артикул
|
||||
if ($this->isNewRecord) {
|
||||
// Новая запись
|
||||
|
||||
if ($model = self::searchByCatn($catn)) {
|
||||
// Продукт уже существует
|
||||
|
||||
return $model;
|
||||
$this->stts = $this->stts ?? 'inactive';
|
||||
}
|
||||
|
||||
// Запись пустого продукта
|
||||
return self::writeEmpty($catn);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$models = [];
|
||||
|
||||
foreach ($oemn as $catn) {
|
||||
// Перебор всех найденных артикулов
|
||||
|
||||
if ($model = self::searchByCatn($catn)) {
|
||||
// Продукт уже существует
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Запись
|
||||
if ($model = self::writeEmpty($catn)) {
|
||||
// Записано
|
||||
|
||||
// Запись в массив сохранённых моделей
|
||||
$models[] = $model;
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись пустого продукта
|
||||
* Запись пустого товара
|
||||
*/
|
||||
public static function writeEmpty(string $catn): ?self
|
||||
public static function writeEmpty(string $catn, string $prod = 'Неизвестный', bool $active = false): ?self
|
||||
{
|
||||
// Инициализация
|
||||
$model = new self;
|
||||
|
||||
// Настройки
|
||||
$model->catn = $catn;
|
||||
$model->prod = $prod;
|
||||
$model->stts = $active ? 'active' : 'inactive';
|
||||
|
||||
// Запись
|
||||
return $model->save() ? $model : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск OEM номеров
|
||||
*
|
||||
* @param string $oemn Необработанная строка с OEM-номерами
|
||||
* @param string $delimiters Разделители
|
||||
*
|
||||
* @todo НЕ ЗАБЫТЬ СДЕЛАТЬ НАСТРОЙКУ РАЗДЕЛИТЕЛЕЙ
|
||||
*
|
||||
* @return array OEM-номера
|
||||
*/
|
||||
public static function searchOemn(string $oemn, string $delimiters = '\s\+\/,'): array
|
||||
{
|
||||
// Инициализация
|
||||
$catn = [];
|
||||
|
||||
// Конвертация
|
||||
preg_match_all("/[^$delimiters]+/", $oemn, $catn);
|
||||
|
||||
return $catn[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Импорт изображений
|
||||
*
|
||||
@@ -266,12 +255,12 @@ class Product extends Document
|
||||
*/
|
||||
public function importImages(): int
|
||||
{
|
||||
// Инициализация
|
||||
$amount = 0;
|
||||
|
||||
if ($this->validate()) {
|
||||
// Проверка пройдена
|
||||
|
||||
// Инициализация
|
||||
$amount = 0;
|
||||
|
||||
foreach ($this->file_image as $file) {
|
||||
// Перебор обрабатываемых изображений
|
||||
|
||||
@@ -285,7 +274,6 @@ class Product extends Document
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (!file_exists(YII_PATH_PUBLIC . $catalog_h150 = '/img/products/' . $this->_key . '/h150')) {
|
||||
// Директория для обложек изображений продукта не найдена
|
||||
|
||||
@@ -307,16 +295,19 @@ class Product extends Document
|
||||
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 ?? [],
|
||||
$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()) {
|
||||
@@ -328,94 +319,23 @@ class Product extends Document
|
||||
}
|
||||
}
|
||||
|
||||
return $amount;
|
||||
}
|
||||
if ($this->hasErrors()) {
|
||||
// Получены ошибки
|
||||
|
||||
/**
|
||||
* Импорт товаров
|
||||
*
|
||||
* На данный момент обрабатывает только импорт из
|
||||
* файлов с расширением .excel
|
||||
*/
|
||||
public function importExcel(): bool
|
||||
{
|
||||
// Инициализация
|
||||
$data = [];
|
||||
$amount = 0;
|
||||
foreach ($this->getErrors() as $attribute => $errors) {
|
||||
// Перебор атрибутов
|
||||
|
||||
if ($this->validate()) {
|
||||
foreach ($this->file_excel as $file) {
|
||||
// Перебор файлов
|
||||
foreach ($errors as $error) {
|
||||
// Перебор ошибок атрибутов
|
||||
|
||||
// Инициализация
|
||||
$dir = '../assets/import/' . date('Y_m_d#H-i', time()) . '/excel/';
|
||||
$label = $this->getAttributeLabel($attribute);
|
||||
|
||||
// Сохранение на диск
|
||||
if (!file_exists($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
$file->saveAs($path = $dir . $file->baseName . '.' . $file->extension);
|
||||
|
||||
$data[] = Excel::import($path, [
|
||||
'setFirstRecordAsKeys' => true,
|
||||
'setIndexSheetByName' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
foreach ($data as $data) {
|
||||
// Перебор конвертированных файлов
|
||||
|
||||
if (count($data) < 1) {
|
||||
// Не найдены строки с товарами
|
||||
|
||||
$this->addError('erros', 'Не удалось найти данные товаров');
|
||||
} else {
|
||||
// Перебор найденных товаров
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification::_write("$label: $error", type: Notification::TYPE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// Деинициализация
|
||||
$this->file_excel = '';
|
||||
|
||||
static::afterImportExcel($amount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->addError('erros', 'Неизвестная ошибка');
|
||||
|
||||
static::afterImportExcel($amount);
|
||||
|
||||
return false;
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,7 +346,7 @@ class Product extends Document
|
||||
*
|
||||
* @todo Переделать нормально
|
||||
*/
|
||||
public static function searchByCatn(string $catn, int $limit = 1, array $select = []): static|array|null
|
||||
public static function searchByCatn(string $catn, int $limit = 999, array $select = []): static|array|null
|
||||
{
|
||||
if ($limit <= 1) {
|
||||
return static::findOne(['catn' => $catn]);
|
||||
@@ -450,19 +370,21 @@ class Product extends Document
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по каталожному номеру (через представления)
|
||||
* Поиск по каталожному номеру и производителю
|
||||
*
|
||||
* Ищет продукт и возвращает его,
|
||||
* либо выполняет поиск через представление
|
||||
*
|
||||
* @todo Переделать нормально
|
||||
*/
|
||||
public static function searchByPartialCatn(string $catn, int $limit = 1, array $select = []): static|array|null
|
||||
public static function searchByCatnAndProd(string $catn, string $prod, int $limit = 1, array $select = []): static|array|null
|
||||
{
|
||||
if ($limit <= 1) {
|
||||
return static::findOne(['catn' => $catn, 'prod' => $prod]);
|
||||
}
|
||||
|
||||
$query = self::find()
|
||||
->for('product')
|
||||
->in('product_search')
|
||||
->search(['catn' => $catn])
|
||||
->where(['catn' => $catn, 'prod' => $prod])
|
||||
->limit($limit)
|
||||
->select($select)
|
||||
->createCommand()
|
||||
@@ -479,40 +401,274 @@ class Product extends Document
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывается после загрузки поставок из excel-документа
|
||||
* Поиск по каталожному номеру (через представления)
|
||||
*
|
||||
* @param int $amount Количество
|
||||
* Ищет продукт и возвращает его,
|
||||
* либо выполняет поиск через представление
|
||||
*
|
||||
* @todo Переделать нормально
|
||||
*/
|
||||
public static function afterImportExcel(int $amount = 0): bool
|
||||
public static function searchByPartialCatn(string $catn, string $stts = 'active', int $limit = 1, array $select = []): static|array|null
|
||||
{
|
||||
// Инициализация
|
||||
$model = new Notification;
|
||||
$date = date('H:i d.m.Y', time());
|
||||
$query = self::find()
|
||||
->for('product')
|
||||
->in('product_search')
|
||||
->where(['stts' => $stts])
|
||||
->search(<<<AQL
|
||||
SEARCH ANALYZER(
|
||||
LEVENSHTEIN_MATCH(
|
||||
product.catn,
|
||||
"$catn",
|
||||
3,
|
||||
true
|
||||
)
|
||||
OR STARTS_WITH(
|
||||
product.catn,
|
||||
"$catn"
|
||||
),
|
||||
"::text_en_no_stem")
|
||||
AQL)
|
||||
->limit($limit)
|
||||
->orderBy('BM25(product) DESC')
|
||||
->select($select)
|
||||
->createCommand()
|
||||
->execute()
|
||||
->getAll();
|
||||
|
||||
// Настройка
|
||||
$model->text = yii::$app->controller->renderPartial('/notification/system/afterImportExcel', compact('amount', 'date'));
|
||||
$model->type = $model::TYPE_NOTICE;
|
||||
foreach ($query as &$attribute) {
|
||||
// Приведение всех свойств в массив и очистка от лишних данных
|
||||
|
||||
// Отправка
|
||||
return (bool) $model->write();
|
||||
$attribute = $attribute->getAll();
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывается после загрузки поставок из 1С
|
||||
* Найти по идентификатору поставки
|
||||
*
|
||||
* @param int $amount Количество
|
||||
* @param string|null $_id Идентификатор поставки
|
||||
*
|
||||
* @return array|null Товар (Product)
|
||||
*/
|
||||
public static function afterImportOnec(): bool
|
||||
public static function searchBySupplyId(string $_id): ?array
|
||||
{
|
||||
return static::searchByEdge(
|
||||
from: 'supply',
|
||||
to: 'product',
|
||||
edge: 'supply_edge_product',
|
||||
direction: 'INBOUND',
|
||||
subquery_where: [
|
||||
[
|
||||
'supply_edge_product._from == "' . $_id . '"'
|
||||
],
|
||||
[
|
||||
'supply_edge_product._to == product._id'
|
||||
]
|
||||
],
|
||||
subquery_select: 'product',
|
||||
where: 'supply_edge_product[0]._id != null',
|
||||
limit: 1,
|
||||
select: 'supply_edge_product[0]'
|
||||
)[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подключение аналога
|
||||
*
|
||||
* @param Product $target Товар который надо подключить
|
||||
*
|
||||
* @return ProductEdgeProductGroup|null Ребро между товаром и группой, если создалось
|
||||
*/
|
||||
public function connect(Product $product): ?ProductEdgeProductGroup
|
||||
{
|
||||
if (!$group = ProductGroup::searchByProduct($this)) {
|
||||
// Не найдена группа товаров
|
||||
|
||||
// Запись новой группы
|
||||
$group = ProductGroup::writeEmpty(active: true);
|
||||
|
||||
// Запись товара в группу
|
||||
$group->writeProduct($this);
|
||||
}
|
||||
|
||||
if ($_group = ProductGroup::searchByProduct($product)) {
|
||||
// Найдена другая группа у товара который надо добавить в группу
|
||||
|
||||
// Перенос всех участников (включая целевой товар)
|
||||
return $group->transfer($_group);
|
||||
} else {
|
||||
// Не найдена группа у товара который надо добавить в группу
|
||||
|
||||
// Запись целевого товара в группу
|
||||
return $group->writeProduct($product);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отключение аналога
|
||||
*
|
||||
* @return bool Статус выполнения
|
||||
*/
|
||||
public function disconnect(): bool
|
||||
{
|
||||
if ($group = ProductGroup::searchByProduct($this)) {
|
||||
// Найдена группа товаров
|
||||
|
||||
// Удаление из группы
|
||||
$group->deleteProduct($this);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// Не найдена группа товаров
|
||||
|
||||
// Заебись
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка на уникальность
|
||||
*
|
||||
* @return bool|static Товар, если найден
|
||||
*
|
||||
* @todo
|
||||
* 1. Обработка дубликатов
|
||||
*/
|
||||
public function validateForUniqueness(): bool|static
|
||||
{
|
||||
if ($supplies = self::search(['catn' => $this->catn, 'prod' => $this->prod], limit: 100)) {
|
||||
// Найдены поставки с таким же артикулом (catn) и производителем (prod)
|
||||
|
||||
// if (count($supplies) > 1) throw new exception ('В базе данных имеется более чем один дубликат', 500);
|
||||
if (count($supplies) > 1) return false;
|
||||
|
||||
// Запись обрабатываемой поставки
|
||||
$supply = $supplies[0];
|
||||
|
||||
// Возврат (найден дубликат в базе данных)
|
||||
return $supply;
|
||||
}
|
||||
|
||||
// Возврат (подразумевается отсутствие дубликатов в базе данных)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация продукта
|
||||
*
|
||||
* @param string $catn Артикул, каталожный номер
|
||||
*/
|
||||
public static function initEmpty(string $catn, string $prod): Supply|array
|
||||
{
|
||||
$oemn = self::searchOemn($catn);
|
||||
|
||||
if (count($oemn) === 1) {
|
||||
// Передан только один артикул
|
||||
|
||||
if ($model = Product::searchByCatnAndProd($catn, $prod)) {
|
||||
// Продукт уже существует
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
// Запись пустого продукта
|
||||
return Product::writeEmpty($catn, $prod);
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$model = new Notification;
|
||||
$date = date('H:i d.m.Y', time());
|
||||
$models = [];
|
||||
|
||||
// Настройка
|
||||
$model->text = yii::$app->controller->renderPartial('/notification/system/afterImportOnec', compact('amount', 'date'));
|
||||
$model->type = $model::TYPE_NOTICE;
|
||||
foreach ($oemn as $catn) {
|
||||
// Перебор всех найденных артикулов
|
||||
|
||||
// Отправка
|
||||
return (bool) $model->write();
|
||||
if ($model = Product::searchByCatnAndProd($catn, $prod)) {
|
||||
// Продукт уже существует
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Запись
|
||||
if ($model = Product::writeEmpty($catn, $prod)) {
|
||||
// Записано
|
||||
|
||||
// Запись в массив сохранённых моделей
|
||||
$models[] = $model;
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Активация
|
||||
*
|
||||
* @return bool Статус выполнения
|
||||
*/
|
||||
public function activate(): bool
|
||||
{
|
||||
$this->stts = 'active';
|
||||
|
||||
if ($this->update() > 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти товары по группе
|
||||
*
|
||||
* @param string|null $_id Идентификатор группы
|
||||
*
|
||||
* @return array|null Товары (Product)
|
||||
*/
|
||||
public static function searchByProductGroup(string $_id): ?array
|
||||
{
|
||||
return static::searchByEdge(
|
||||
from: 'product_group',
|
||||
to: 'product',
|
||||
edge: 'product_edge_product_group',
|
||||
direction: 'INBOUND',
|
||||
subquery_where: [
|
||||
[
|
||||
'product_edge_product_group._from == "' . $_id . '"'
|
||||
]
|
||||
],
|
||||
subquery_select: 'product',
|
||||
where: 'product_edge_product_group[0]._id != null',
|
||||
limit: 1,
|
||||
select: 'product_edge_product_group[0]'
|
||||
)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация обложки для товара
|
||||
*
|
||||
* Ищет логотип в нужной категории (размере) для выбранного производителя
|
||||
*
|
||||
* @param string $prod Производитель
|
||||
* @param int $size Размерная группа
|
||||
*
|
||||
* @return string Относительный путь до изображения от публичной корневой папки
|
||||
*/
|
||||
public static function cover(string $prod, int $size = 150): string
|
||||
{
|
||||
if ($size === 0) $size = '';
|
||||
else $size = "h$size/";
|
||||
|
||||
// Инициализация пути
|
||||
$path = "/img/covers/$size" . strtolower($prod);
|
||||
|
||||
// Поиск файла и возврат
|
||||
if (file_exists(YII_PATH_PUBLIC . DIRECTORY_SEPARATOR . $path . '.jpg')) return $path . '.jpg';
|
||||
else if (file_exists(YII_PATH_PUBLIC . DIRECTORY_SEPARATOR . $path . '.jpeg')) return $path . '.jpeg';
|
||||
else if (file_exists(YII_PATH_PUBLIC . DIRECTORY_SEPARATOR . $path . '.png')) return $path . '.png';
|
||||
|
||||
// Возврат изображения по умолчанию
|
||||
return "/img/covers/$size" . 'product.png';
|
||||
}
|
||||
}
|
||||
|
@@ -10,4 +10,49 @@ class ProductEdgeProduct extends Edge
|
||||
{
|
||||
return 'product_edge_product';
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти все соединения
|
||||
*
|
||||
* @param string $_key Ключ товара
|
||||
* @param int $limit Ограничение по количеству
|
||||
*
|
||||
* @return array|null Найденные соединения (массив с ключами - "_key")
|
||||
*/
|
||||
public static function searchConnections(string $_key, int $limit = 30): ?array
|
||||
{
|
||||
// Инициализация буфера возврата
|
||||
$return = [];
|
||||
|
||||
// Поиск аналогов
|
||||
$edges = self::find()->where(['_from' => Product::collectionName() . "/$_key"])->limit($limit)->all();
|
||||
|
||||
foreach ($edges as $edge) {
|
||||
// Перебор найденных рёбер
|
||||
|
||||
// Извлечение ключа
|
||||
preg_match_all('/\w+\/([0-9]+)/', $edge->_to, $matches);
|
||||
|
||||
// Запись артикула в буфер вывода
|
||||
$return[] = $matches[1][0];
|
||||
}
|
||||
|
||||
// Перерасчет ограничения по количеству
|
||||
$limit -= count($edges);
|
||||
|
||||
// Поиск аналогов (с обратной привязкой)
|
||||
$edges = self::find()->where(['_to' => Product::collectionName() . "/$_key"])->limit($limit)->all();
|
||||
|
||||
foreach ($edges as $edge) {
|
||||
// Перебор найденных рёбер
|
||||
|
||||
// Извлечение ключа
|
||||
preg_match_all('/\w+\/([0-9]+)/', $edge->_from, $matches);
|
||||
|
||||
// Запись артикула в буфер вывода
|
||||
$return[] = $matches[1][0];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -10,4 +10,17 @@ class ProductEdgeProductGroup extends Edge
|
||||
{
|
||||
return 'product_edge_product_group';
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по товару
|
||||
*
|
||||
* @param Product $product Товар
|
||||
* @param int $amount Ограничение по максимальному количеству
|
||||
*
|
||||
* @return null|self Ребро, если найдено
|
||||
*/
|
||||
public static function searchByProduct(Product $product, int $limit = 1): ?self
|
||||
{
|
||||
return self::find()->where(['_from' => $product->readId()])->limit($limit)->all()[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
@@ -4,15 +4,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
|
||||
use carono\exchange1c\interfaces\GroupInterface;
|
||||
|
||||
use Zenwalker\CommerceML\Model\Group;
|
||||
|
||||
/**
|
||||
* Группировка продуктов
|
||||
* Группировка продуктов для соединения их в аналоги
|
||||
*/
|
||||
class ProductGroup extends Document implements GroupInterface
|
||||
{
|
||||
use SearchByEdge;
|
||||
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
@@ -29,7 +33,7 @@ class ProductGroup extends Document implements GroupInterface
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'name'
|
||||
'stts'
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -42,7 +46,7 @@ class ProductGroup extends Document implements GroupInterface
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'name' => 'Название (name)'
|
||||
'stts' => 'Статус'
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -55,23 +59,152 @@ class ProductGroup extends Document implements GroupInterface
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
// [
|
||||
// 'name',
|
||||
// 'required',
|
||||
// 'message' => 'Заполните поле: {attribute}'
|
||||
// ]
|
||||
[
|
||||
'stts',
|
||||
'string',
|
||||
'length' => [4, 20],
|
||||
'message' => '{attribute} должен быть строкой от 4 до 20 символов'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись пустой группы
|
||||
*
|
||||
* @param bool $active Статус активации
|
||||
*
|
||||
* @return self Группа товаров, если создана
|
||||
*/
|
||||
public static function writeEmpty(bool $active = false): ?self
|
||||
{
|
||||
// Инициализация
|
||||
$model = new self;
|
||||
|
||||
// Настройки
|
||||
$model->stts = $active ? 'active' : 'inactive';
|
||||
|
||||
// Запись
|
||||
return $model->save() ? $model : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись члена группы
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function writeMember(Product $member): ProductEdgeProductGroup
|
||||
{
|
||||
return ProductEdgeProductGroup::write($member->readId(), $this->readId(), 'member');
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись товара в группу
|
||||
*
|
||||
* @param Product $product Товар
|
||||
*/
|
||||
public function writeProduct(Product $product): ?ProductEdgeProductGroup
|
||||
{
|
||||
// Запись товара в группу
|
||||
$edge = ProductEdgeProductGroup::write($product->readId(), $this->readId(), data: ['type' => 'member']);
|
||||
|
||||
// Запись в журнал
|
||||
$this->journal('write member', [
|
||||
'product' => $product->readId()
|
||||
]);
|
||||
|
||||
return $edge;
|
||||
}
|
||||
/**
|
||||
* Удаление товара из группы
|
||||
*
|
||||
* @param Product $product Товар
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProduct(Product $product): void
|
||||
{
|
||||
// Удаление товара из группы (подразумевается, что будет только одно)
|
||||
foreach (ProductEdgeProductGroup::searchByVertex($product->readId(), $this->readId(), filter: ['type' => 'member']) as $edge) $edge->delete();
|
||||
|
||||
// Запись в журнал
|
||||
$this->journal('delete member', [
|
||||
'product' => $product->readId()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти рёбра до товаров
|
||||
*
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*/
|
||||
public function searchEdges(int $limit = 100, int $page = 1): ?array
|
||||
{
|
||||
return ProductEdgeProductGroup::searchByDirection($this->readId(), 'INBOUND', where: ['type' => 'member'], limit: $limit, page: $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Прочитать связанные товары
|
||||
*
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*/
|
||||
public function searchProducts(int $limit = 100, int $page = 1): ?array
|
||||
{
|
||||
// Инициализация буфера товаров
|
||||
$products = [];
|
||||
|
||||
foreach ($this->searchEdges($limit, $page) as $edge) {
|
||||
// Перебор рёбер
|
||||
|
||||
$products[] = Product::searchById($edge->_from);
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Перенос членов группы из другой
|
||||
*
|
||||
* @param ProductGroup $group Группа из которой нужен перенос
|
||||
*
|
||||
* @return null|int Количество перенесённых товаров, если произведён перенос
|
||||
*/
|
||||
public function transfer(ProductGroup $group): ?int
|
||||
{
|
||||
// Проверка на то, что запрошен перенос "из себя в себя"
|
||||
if ($this->readId() === $group->readId()) return null;
|
||||
|
||||
// Инициализация счётчика записанных товаров
|
||||
$transfered = 0;
|
||||
|
||||
// Перенос
|
||||
foreach ($group->searchProducts() as $product) if ($this->writeProduct($product)) ++$transfered;
|
||||
|
||||
// Деактивация целевой группы (пустой)
|
||||
$group->deactivate();
|
||||
|
||||
// Запись в журнал
|
||||
$this->journal('transfer', [
|
||||
'from' => $group->readId()
|
||||
]);
|
||||
|
||||
return $transfered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Деактивация
|
||||
*
|
||||
* @return bool Статус выполнения
|
||||
*/
|
||||
public function deactivate(): bool
|
||||
{
|
||||
$this->stts = 'inactive';
|
||||
|
||||
if ($this->update() > 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись рёбер групп
|
||||
*
|
||||
@@ -171,4 +304,29 @@ class ProductGroup extends Document implements GroupInterface
|
||||
{
|
||||
return static::findOne(['onec_id' => $onec_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти по идентификатору товара
|
||||
*
|
||||
* @param Product $product Товар
|
||||
*
|
||||
* @return self|null Группа (ProductGroup)
|
||||
*/
|
||||
public static function searchByProduct(Product $product): ?self
|
||||
{
|
||||
return static::searchByEdge(
|
||||
from: 'product',
|
||||
to: 'product_group',
|
||||
edge: 'product_edge_product_group',
|
||||
direction: 'INBOUND',
|
||||
subquery_where: [
|
||||
[
|
||||
'product_edge_product_group._from == "' . $product->readId() . '"'
|
||||
]
|
||||
],
|
||||
subquery_select: 'product_group',
|
||||
where: 'product_edge_product_group[0]._id != null',
|
||||
limit: 1
|
||||
)[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use carono\exchange1c\interfaces\DocumentInterface;
|
||||
|
||||
class Purchase extends Document implements DocumentInterface
|
||||
{
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'purchase';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DocumentInterface[]
|
||||
*/
|
||||
public static function findDocuments1c(): ?array
|
||||
{
|
||||
return self::find()->andWhere(['status_id' => 2])->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OfferInterface[]
|
||||
*/
|
||||
public function getOffers1c(): mixed
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRequisites1c(): mixed
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получаем контрагента у документа
|
||||
*
|
||||
* @return PartnerInterface
|
||||
*/
|
||||
public function getPartner1c(): Account
|
||||
{
|
||||
// !!!!!!!!!!!!!!!!!!!
|
||||
return $this->user ?? new Account;
|
||||
}
|
||||
|
||||
public function getExportFields1c($context = null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращаем имя поля в базе данных, в котором хранится ID из 1с
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getIdFieldName1c()
|
||||
{
|
||||
return 'onec["Ид"]';
|
||||
}
|
||||
|
||||
public function setRaw1cData($cml, $object): void
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
class PurchaseEdgeSupply extends Edge
|
||||
{
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'purchase_edge_supply';
|
||||
}
|
||||
}
|
100
mirzaev/skillparts/system/models/Request.php
Normal file
100
mirzaev/skillparts/system/models/Request.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use yii;
|
||||
use yii\web\UploadedFile;
|
||||
use yii\imagine\Image;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
use moonland\phpexcel\Excel;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Запрос (регистрация поставщика)
|
||||
*
|
||||
* Представляет собой набор данных от поставщика для регистрации
|
||||
*/
|
||||
class Request extends Document
|
||||
{
|
||||
/**
|
||||
* Файл с данными ("Карточка предприятия")
|
||||
*/
|
||||
public string|array|null $file = null;
|
||||
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'request';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'name',
|
||||
'phon',
|
||||
'mail',
|
||||
'file'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'name' => 'ФИО',
|
||||
'phon' => 'Телефон',
|
||||
'mail' => 'Почта',
|
||||
'file' => 'Карточка предприятия'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*
|
||||
* @todo Правило для всех трёх габаритов
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
[
|
||||
'file',
|
||||
'mail'
|
||||
],
|
||||
'required',
|
||||
'message' => 'Обязательные поля: {attribute}'
|
||||
],
|
||||
[
|
||||
'file',
|
||||
'file',
|
||||
'skipOnEmpty' => false,
|
||||
// 'extensions' => 'xlsx',
|
||||
'checkExtensionByMimeType' => false,
|
||||
'maxFiles' => 1,
|
||||
'maxSize' => 1024 * 1024 * 30,
|
||||
// 'wrongExtension' => 'Разрешены только документы в формате: ".xlsx"',
|
||||
'message' => 'Проблема при чтении документа'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
@@ -5,9 +5,15 @@ declare(strict_types=1);
|
||||
namespace app\models;
|
||||
|
||||
use yii;
|
||||
use yii\web\IdentityInterface;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
use app\models\Dellin as DellinModel;
|
||||
use app\models\Settings;
|
||||
use app\models\connection\Dellin;
|
||||
|
||||
use datetime;
|
||||
use exception;
|
||||
use throwable;
|
||||
|
||||
/**
|
||||
* Поиск
|
||||
@@ -35,6 +41,7 @@ class Search extends Document
|
||||
parent::attributes(),
|
||||
[
|
||||
'text',
|
||||
'type',
|
||||
'ipv4',
|
||||
'head'
|
||||
]
|
||||
@@ -50,6 +57,7 @@ class Search extends Document
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'text' => 'Текст',
|
||||
'type' => 'Тип',
|
||||
'ipv4' => 'IPv4',
|
||||
'head' => 'Заголовки'
|
||||
]
|
||||
@@ -86,9 +94,10 @@ class Search extends Document
|
||||
* Запись
|
||||
*
|
||||
* @param string $text Текст запроса
|
||||
* @param string $type Тип запроса
|
||||
* @param Account|null $account Пользователь совершивший запрос
|
||||
*/
|
||||
public static function write(string $text, Account|null $account = null): ?self
|
||||
public static function write(string $text, string $type = 'general', Account|null $account = null): ?self
|
||||
{
|
||||
// Инициализация
|
||||
$vertex = new self;
|
||||
@@ -96,6 +105,7 @@ class Search extends Document
|
||||
|
||||
// Настройки
|
||||
$vertex->text = $text;
|
||||
$vertex->type = $type;
|
||||
|
||||
if ($vertex->save()) {
|
||||
// Поиск записан
|
||||
@@ -106,4 +116,472 @@ class Search extends Document
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Поиск содержимого поиска (продуктов)
|
||||
*
|
||||
* @todo В будущем возможно заказ не только поставок реализовать
|
||||
* Переписать реестр и проверку на дубликаты, не понимаю как они работают
|
||||
*/
|
||||
public static function content(array $products, int $limit = 50, int $page = 1): Supply|int|array|null
|
||||
{
|
||||
// Инициализация буфера вывода
|
||||
$response = $products;
|
||||
|
||||
// Генерация сдвига по запрашиваемым данным (система страниц)
|
||||
$offset = $limit * ($page - 1);
|
||||
|
||||
foreach ($response as &$row) {
|
||||
// Перебор продуктов
|
||||
|
||||
if ($row instanceof Product) {
|
||||
// В массиве объект - инстанция товара
|
||||
|
||||
// Преобразование к массиву в буфер (унификация данных)
|
||||
$_row = $row->attributes;
|
||||
} else {
|
||||
// В массиве не товар (подразумевается, что это массив с параметрами)
|
||||
|
||||
// Запись в буфер (унификация данных)
|
||||
$_row = $row;
|
||||
}
|
||||
|
||||
// Поиск поставок привязанных к продуктам
|
||||
$connections = Supply::searchByEdge(
|
||||
from: 'product',
|
||||
to: 'supply',
|
||||
edge: 'supply_edge_product',
|
||||
limit: $limit,
|
||||
offset: $offset,
|
||||
direction: 'OUTBOUND',
|
||||
subquery_where: [
|
||||
['product._key' => $_row['_key']],
|
||||
['supply.catn == product.catn'],
|
||||
['supply_edge_product.type' => 'connect']
|
||||
],
|
||||
where: 'supply._id == supply_edge_product[0]._from',
|
||||
select: '{supply, supply_edge_product}'
|
||||
);
|
||||
|
||||
// Инициализация буфера
|
||||
$buffer_connections = [];
|
||||
|
||||
if (count($connections) === 100) {
|
||||
// Если в базе данных хранится много поставок
|
||||
|
||||
// Инициализация
|
||||
$_row['overload'] = true;
|
||||
}
|
||||
|
||||
foreach ($connections as $key => &$connection) {
|
||||
// Перебор поставок
|
||||
|
||||
if ($cost = $connection['supply']['cost'] < 1) {
|
||||
// Цена меньше единицы (подразумевается как ошибка)
|
||||
|
||||
// Скрыть из выдачи
|
||||
unset($connections[$key]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Инициализация аккаунта
|
||||
$connection['account'] = Account::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
|
||||
|
||||
// Инициализация продукта
|
||||
$connection['product'] = Product::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
|
||||
|
||||
try {
|
||||
// Доставка "auto"
|
||||
|
||||
try {
|
||||
$from = (int) (Warehouse::searchBySupply(Supply::searchByCatnAndProd($connection['supply']['catn'], $connection['supply']['prod']))[0]->trmn ?? Settings::searchActive()?->delivery_from_default ?? 36);
|
||||
} catch (exception $e) {
|
||||
$from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default;
|
||||
}
|
||||
|
||||
try {
|
||||
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
|
||||
} catch (Exception $e) {
|
||||
$to = 36;
|
||||
}
|
||||
|
||||
if (DellinModel::searchByTerminalId($from)->data['cityID'] === DellinModel::searchByTerminalId($to)->data['cityID']) {
|
||||
// Доставка в пределах города
|
||||
|
||||
$connection['delivery'] = [
|
||||
'price' => [
|
||||
'all' => 1
|
||||
],
|
||||
'ready' => 1,
|
||||
'type' => 'auto'
|
||||
];
|
||||
|
||||
goto skip_avia;
|
||||
}
|
||||
// Доставка в другие города
|
||||
|
||||
// Инициализация буфера доставки
|
||||
$buffer_connection = $connection['product']['bffr']["$from-$to"] ?? null;
|
||||
|
||||
if (isset($buffer_connection) && !empty($buffer_connection['data']) && time() < $buffer_connection['expires'] ?? 0) {
|
||||
// Найдены данные доставки в буфере
|
||||
// и срок хранения не превышен, информация актуальна
|
||||
|
||||
// Запись в буфер вывода
|
||||
$connection['delivery'] = $buffer_connection['data'];
|
||||
$connection['delivery']['type'] = 'auto';
|
||||
} else {
|
||||
// Инициализация инстанции продукта в базе данных
|
||||
$product = Product::searchByCatnAndProd($connection['product']['catn'], $connection['product']['prod']);
|
||||
|
||||
if ($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)
|
||||
)) {
|
||||
// Получены данные доставки
|
||||
|
||||
// Инициализация доставки Dellin (автоматическая)
|
||||
$product->bffr = [
|
||||
"$from-$to" => [
|
||||
'data' => $connection['delivery'],
|
||||
'expires' => time() + 86400
|
||||
]
|
||||
] + ($product->bffr ?? []);
|
||||
|
||||
// Отправка в базу данных
|
||||
$product->update();
|
||||
}
|
||||
|
||||
// Запись типа доставки
|
||||
$connection['delivery']['type'] = 'auto';
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$connection['delivery']['error'] = true;
|
||||
|
||||
// var_dump($e->getMessage());
|
||||
// var_dump($e->getTrace());
|
||||
// var_dump($e->getFile());
|
||||
|
||||
// var_dump(json_decode($e->getMessage(), true)['errors']);
|
||||
// die;
|
||||
} finally {
|
||||
// echo $connection['delivery']['price']['all'];
|
||||
// Инициализация цены (цена поставки + цена доставки + наша наценка)
|
||||
$connection['cost'] = $cost + ($connection['delivery']['price']['all'] ?? $connection['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
|
||||
}
|
||||
|
||||
// Инициализация версии для рассчета доставки по воздуху
|
||||
$buffer_delivery_avia = $connection;
|
||||
|
||||
try {
|
||||
// Доставка "avia"
|
||||
|
||||
if ($cost = $connection['supply']['cost'] < 1) {
|
||||
// Цена меньше единицы (подразумевается как ошибка)
|
||||
// Этот код не будет выполняться, так как цена одна на обе позиции и аналогичная проверка выше уже есть
|
||||
// Однако я это оставлю для возможных доработок в будущем
|
||||
|
||||
// Скрыть из выдачи
|
||||
unset($connections[$key]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$from = (int) (Warehouse::searchBySupply(Supply::searchByCatnAndProd($connection['supply']['catn'], $connection['supply']['prod']))[0]->trmn ?? Settings::searchActive()?->delivery_from_default ?? 36);
|
||||
} catch (Exception $e) {
|
||||
$from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default;
|
||||
}
|
||||
|
||||
try {
|
||||
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
|
||||
} catch (Exception $e) {
|
||||
$to = 36;
|
||||
}
|
||||
|
||||
// Доставка в другие города
|
||||
|
||||
// Инициализация буфера доставки
|
||||
$buffer_connection = $connection['product']['bffr']["$from-$to-avia"] ?? null;
|
||||
|
||||
if (isset($buffer_connection) && !empty($buffer_connection['data']) && time() < $buffer_connection['expires'] ?? 0) {
|
||||
// Найдены данные доставки в буфере
|
||||
// и срок хранения не превышен, информация актуальна
|
||||
|
||||
// Запись в буфер вывода
|
||||
$buffer_delivery_avia['delivery'] = $buffer_connection['data'];
|
||||
$buffer_delivery_avia['delivery']['type'] = 'avia';
|
||||
} else {
|
||||
// Инициализация инстанции продукта в базе данных
|
||||
$product = Product::searchByCatnAndProd($buffer_delivery_avia['product']['catn'], $connection['product']['prod']);
|
||||
|
||||
if ($buffer_delivery_avia['delivery'] = Dellin::calcDeliveryAdvanced(
|
||||
$from,
|
||||
$to,
|
||||
(int) ($buffer_delivery_avia['product']['wght'] ?? 0),
|
||||
(int) ($buffer_delivery_avia['product']['dmns']['x'] ?? 0),
|
||||
(int) ($buffer_delivery_avia['product']['dmns']['y'] ?? 0),
|
||||
(int) ($buffer_delivery_avia['product']['dmns']['z'] ?? 0),
|
||||
avia: true
|
||||
)) {
|
||||
// Получены данные доставки
|
||||
|
||||
// Инициализация доставки Dellin (автоматическая)
|
||||
$product->bffr = [
|
||||
"$from-$to-avia" => [
|
||||
'data' => $buffer_delivery_avia['delivery'],
|
||||
'expires' => time() + 86400
|
||||
]
|
||||
] + ($product->bffr ?? []);
|
||||
|
||||
// Отправка в базу данных
|
||||
$product->update();
|
||||
}
|
||||
|
||||
// Запись типа доставки
|
||||
$buffer_delivery_avia['delivery']['type'] = 'avia';
|
||||
}
|
||||
} catch (exception $e) {
|
||||
$buffer_delivery_avia['delivery']['error'] = true;
|
||||
|
||||
// var_dump($e->getMessage());
|
||||
// var_dump($e->getTrace());
|
||||
// var_dump($e->getFile());
|
||||
|
||||
// var_dump(json_decode($e->getMessage(), true)['errors']);
|
||||
// die;
|
||||
} finally {
|
||||
if (!isset($buffer_delivery_avia['delivery']['error']) || $buffer_delivery_avia['delivery']['error'] !== true) {
|
||||
// Если рассчиталась доставка самолётом
|
||||
|
||||
// echo $buffer_delivery_avia['delivery']['price']['all']; die;
|
||||
|
||||
// Инициализация цены (цена поставки + цена доставки + наша наценка)
|
||||
$buffer_delivery_avia['cost'] = $cost + ($buffer_delivery_avia['delivery']['price']['all'] ?? $buffer_delivery_avia['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
|
||||
|
||||
// Запись в буфер
|
||||
$buffer_connections[] = $buffer_delivery_avia;
|
||||
}
|
||||
}
|
||||
|
||||
// Пропуск доставки "avia"
|
||||
skip_avia:
|
||||
}
|
||||
|
||||
|
||||
// Запись обработанных данных
|
||||
$_row['supplies'] = array_merge($connections, $buffer_connections);
|
||||
|
||||
// Запись из буфера
|
||||
$row = $_row;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация HTML-кода с найденным товаром
|
||||
*
|
||||
* Я сам в ахуе, переделывать не буду
|
||||
*
|
||||
* @param array $row Товар сгенерированный через Search::content()
|
||||
* @param string|null $cover Обложка
|
||||
* @param array $list Реестр найденных товаров
|
||||
* @param bool $analogs Запрошены аналоги (не выведет пустые товары)
|
||||
*
|
||||
* @return string HTML-элемент с товаром
|
||||
*/
|
||||
public static function generate(array &$row, string|null &$cover = null, array &$list = [], bool $analogs = false): string
|
||||
{
|
||||
foreach ($row['imgs'] ?? [] as &$img) {
|
||||
// Перебор изображений для обложки
|
||||
|
||||
if ($img['covr'] ?? false) {
|
||||
// Найдена обложка
|
||||
|
||||
$cover = $img['h150'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($cover)) {
|
||||
// Обложка не инициализирована
|
||||
|
||||
if (!$cover = $imgs[0]['h150'] ?? false) {
|
||||
// Не удалось использовать первое изображение как обложку
|
||||
|
||||
// Запись обложки по умолчанию
|
||||
$cover = Product::cover($row['prod'], 150);
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация буфера с HTML поставок
|
||||
$supplies_html = '';
|
||||
|
||||
// Инициализация блокировщика для пустого блока (на случай, если нет поставок, чтобы не было дубликатов вывода)
|
||||
$empty_block = false;
|
||||
|
||||
// Инициализация счётчика поставок
|
||||
$supplies_amount = count($row['supplies'] ?? []);
|
||||
|
||||
// Инициализация указателя номера цикла
|
||||
$supply_iterator = 1;
|
||||
|
||||
foreach (empty($row['supplies']) || $supplies_amount === 0 ? [null] : $row['supplies'] as &$supply) {
|
||||
// Перебор поставок
|
||||
|
||||
// Запись в список найденных
|
||||
$list[$row['prod']] = [$row['catn']] + (isset($list[$row['prod']]) ? $list[$row['prod']] : []);
|
||||
|
||||
// Инициализация модификатора класса
|
||||
if ($supplies_amount > $supply_iterator) {
|
||||
// Это не последняя строка с товаром и его поставками
|
||||
|
||||
$supply_class_modifier = 'mb-1';
|
||||
} else {
|
||||
// Это последняя строка с товаром и его поставками
|
||||
|
||||
$supply_class_modifier = '';
|
||||
}
|
||||
|
||||
if (is_null($supply)) {
|
||||
// Поставки отсутствуют
|
||||
|
||||
// Генерация данных об отсутствии заказов
|
||||
goto no_supplies;
|
||||
} else {
|
||||
// Обычная обработка поставки
|
||||
|
||||
// Инициализация переменных
|
||||
extract($supply);
|
||||
}
|
||||
|
||||
// Инициализация цены
|
||||
$price_raw = ($supply['cost'] ?? 0) + ($cost ?? 0);
|
||||
// $price = $price_raw . ' ' . $supply_edge_product[0]['onec']['Цены']['Цена']['Валюта'] ?? 'руб';
|
||||
$price = $price_raw . ' руб';
|
||||
|
||||
// Инициализация количества
|
||||
// $amount_raw = $amount = $supply['amnt'] ?? $supply_edge_product[0]['onec']['Количество'] ?? 0;
|
||||
|
||||
// Инициализация цены и её представления
|
||||
$amount_raw = $amount = count(@SupplyEdgeProduct::searchByVertex(@Supply::collectionName() . '/' . $supply['_key'], @Product::searchByCatnAndProd($supply['catn'], $supply['prod'])->readId(), limit: 999)) ?? 1;
|
||||
$amount .= ' шт';
|
||||
|
||||
if ($amount_raw < 1 || $price_raw < 1) {
|
||||
// Количество 0 или цена 0
|
||||
|
||||
// Поставки отстутвуют
|
||||
no_supplies:
|
||||
|
||||
// Проверка на блокировку или запрошены аналоги
|
||||
if ($empty_block || $analogs) continue;
|
||||
|
||||
$supplies_html .= <<<HTML
|
||||
<div class="row $supply_class_modifier m-0 h-100 text-right">
|
||||
<a class="col-auto ml-auto my-auto text-dark" href="/order/new/custom" role="button" onclick="return false;">
|
||||
<small>
|
||||
Заказать поиск у оператора
|
||||
</small>
|
||||
</a>
|
||||
</div>
|
||||
HTML;
|
||||
|
||||
// Запись блокировщика
|
||||
$empty_block = true;
|
||||
|
||||
// Обновление счётчика
|
||||
++$supply_iterator;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Инициализация доставки
|
||||
if (isset($delivery['error']) || $delivery === '?') {
|
||||
// Не удалось рассчитать доставку
|
||||
|
||||
// Инициализация типа доставки
|
||||
$delivery_type = $delivery['type'] ?? 'auto';
|
||||
|
||||
// Инициализация индикатора
|
||||
$delivery_icon = '<i class="mr-1 fas fa-truck"></i>';
|
||||
|
||||
// Инициализация времени
|
||||
$delivery = '?';
|
||||
} else {
|
||||
// Удалось рассчитать доставку
|
||||
|
||||
// Инициализация типа доставки
|
||||
$delivery_type = $delivery['type'] ?? 'auto';
|
||||
|
||||
// Инициализация индикатора
|
||||
$delivery_icon = match ($delivery_type) {
|
||||
'avia' => '<i class="mr-1 fas fa-plane"></i>',
|
||||
default => '<i class="mr-1 fas fa-truck"></i>'
|
||||
};
|
||||
|
||||
if ($delivery['ready'] ?? false) {
|
||||
// Указана дата готовности к получению
|
||||
|
||||
// Инициализация доставки
|
||||
$delivery = $delivery['ready'];
|
||||
} else {
|
||||
// Не указана дата готовности к получению
|
||||
|
||||
// Инициализация даты отправки
|
||||
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 (Throwable $e) {
|
||||
// Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель)
|
||||
|
||||
$delivery_converted = @datetime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspReceiver'])->getTimestamp();
|
||||
}
|
||||
|
||||
// Инициализация доставки
|
||||
$delivery = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация индекса аккаунта
|
||||
$index = $account['indx'] ?? 'Неизвестен';
|
||||
|
||||
// Генерация
|
||||
$supplies_html .= <<<HTML
|
||||
<div class="row $supply_class_modifier m-0 text-right">
|
||||
<small class="ml-auto col-1 ml-2 my-auto pl-2 pr-0">$index</small>
|
||||
<small class="col-1 my-auto pl-2 pr-0 text-center">$amount</small>
|
||||
<small class="col-auto mr-2 my-auto pl-2 pr-0 text-left" title="Ориентировочно">$delivery_icon $delivery дн</small>
|
||||
<b class="col-auto my-auto my-auto text-center">$price</b>
|
||||
<a class="col-1 ml-0 py-2 text-dark d-flex button_white rounded" title="Добавить {$row['catn']} в корзину" role="button" onclick="return cart_write('{$supply['_id']}', '$delivery_type');">
|
||||
<i class="fas fa-cart-arrow-down pr-1 m-auto"></i>
|
||||
</a>
|
||||
</div>
|
||||
HTML;
|
||||
|
||||
// Обновление счётчика
|
||||
++$supply_iterator;
|
||||
}
|
||||
|
||||
return $supplies_html;
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,10 @@ class Settings extends Document
|
||||
parent::attributes(),
|
||||
[
|
||||
'search_period',
|
||||
'search_connect_keep'
|
||||
'search_connect_keep',
|
||||
'delivery_from_default',
|
||||
'addition_global',
|
||||
'delivery_addition_global'
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -40,7 +43,10 @@ class Settings extends Document
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'search_period' => 'Поисковый период',
|
||||
'search_connect_keep' => 'Режим удержания'
|
||||
'search_connect_keep' => 'Режим удержания',
|
||||
'delivery_from_default' => 'Место отправки поставки по умолчанию',
|
||||
'addition_global' => 'Глобальная наценка',
|
||||
'delivery_addition_global' => 'Глобальная надбавка к доставке'
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -55,14 +61,17 @@ class Settings extends Document
|
||||
[
|
||||
[
|
||||
[
|
||||
'search_period'
|
||||
'search_period',
|
||||
'delivery_from_default',
|
||||
'delivery_addition_global'
|
||||
],
|
||||
'integer',
|
||||
'message' => '{attribute} должен хранить цифровое значение'
|
||||
],
|
||||
[
|
||||
[
|
||||
'search_connect_keep'
|
||||
'search_connect_keep',
|
||||
'addition_global'
|
||||
],
|
||||
'string',
|
||||
'message' => '{attribute} должен хранить строковый тип'
|
||||
@@ -70,4 +79,14 @@ class Settings extends Document
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти активную запись с настройками
|
||||
*
|
||||
* @todo Доделать
|
||||
*/
|
||||
public static function searchActive(): ?self
|
||||
{
|
||||
return static::findOne(['active' => true]);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,10 @@ declare(strict_types=1);
|
||||
namespace app\models;
|
||||
|
||||
use app\models\traits\Xml2Array;
|
||||
use carono\exchange1c\interfaces\OfferInterface;
|
||||
use Zenwalker\CommerceML\Model\Offer;
|
||||
|
||||
class SupplyEdgeProduct extends Edge implements OfferInterface
|
||||
use carono\exchange1c\interfaces\OfferInterface;
|
||||
|
||||
class SupplyEdgeProduct extends Edge implements OfferInterface
|
||||
{
|
||||
use Xml2Array;
|
||||
|
||||
@@ -69,6 +69,17 @@ class SupplyEdgeProduct extends Edge implements OfferInterface
|
||||
return self::findOne([self::getIdFieldName1c() => $ocid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по идентификатору поставки
|
||||
*
|
||||
* @param string $_id Идентификатор поставки
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*/
|
||||
public static function searchBySupplyId(string $_id, int $limit = 1): array
|
||||
{
|
||||
return self::find()->where(['_from' => $_id])->limit($limit)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Название поля в котором хранится ID из 1C
|
||||
*/
|
||||
|
@@ -4,10 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use carono\exchange1c\interfaces\GroupInterface;
|
||||
/**
|
||||
* Группировка поставок
|
||||
*/
|
||||
class SupplyGroup extends ProductGroup
|
||||
class SupplyGroup extends ProductGroup implements GroupInterface
|
||||
{
|
||||
/**
|
||||
* Имя коллекции
|
||||
@@ -16,4 +17,9 @@ class SupplyGroup extends ProductGroup
|
||||
{
|
||||
return 'supply_group';
|
||||
}
|
||||
|
||||
|
||||
public static function createTree1c($groups): Document|null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
95
mirzaev/skillparts/system/models/Terminal.php
Normal file
95
mirzaev/skillparts/system/models/Terminal.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
/**
|
||||
* Терминалы выдачи
|
||||
*/
|
||||
class Terminal extends Document
|
||||
{
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'terminal';
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'name',
|
||||
'cntr',
|
||||
'city',
|
||||
'strt',
|
||||
'hous',
|
||||
'offs',
|
||||
'comm',
|
||||
'dell',
|
||||
'hndl'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
[
|
||||
'name',
|
||||
'cntr',
|
||||
'city',
|
||||
'strt',
|
||||
'hous',
|
||||
'offs',
|
||||
'comm'
|
||||
],
|
||||
'string'
|
||||
],
|
||||
[
|
||||
[
|
||||
'dell',
|
||||
'hndl'
|
||||
],
|
||||
'integer'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'name' => 'Название',
|
||||
'cntr' => 'Страна',
|
||||
'city' => 'Город',
|
||||
'strt' => 'Улица',
|
||||
'hous' => 'Дом',
|
||||
'offs' => 'Офис',
|
||||
'comm' => 'Комментарий',
|
||||
'dell' => 'Терминал ДеловыеЛинии для рассчётов доставки',
|
||||
'hndl' => 'Количество дней для обработки после получения от ДеловыеЛинии'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по индентификатору терминала ДеловыеЛинии
|
||||
*
|
||||
* @param string $dell Идентификатор терминала ДеловыеЛинии
|
||||
* @param int $limit Максимальное количество результатов поиска
|
||||
*
|
||||
* @return array|null Терминалы SkillParts, если найдены
|
||||
*
|
||||
* @todo Сделать привязку терминалов SkillParts к нескольким терминалам ДеловыеЛинии и переделать под это поиск
|
||||
*/
|
||||
public static function searchByDellinTerminalId(string $dell, int $limit = 1): ?array
|
||||
{
|
||||
return self::find()->where(['dell' => $dell])->limit($limit)->all();
|
||||
}
|
||||
}
|
287
mirzaev/skillparts/system/models/Warehouse.php
Normal file
287
mirzaev/skillparts/system/models/Warehouse.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use app\models\traits\SearchByEdge;
|
||||
|
||||
/**
|
||||
* Склад
|
||||
*
|
||||
* Хранит в себе связи с инстанциями поставок, а от них и связи со всеми поставками
|
||||
*/
|
||||
class Warehouse extends Document
|
||||
{
|
||||
use SearchByEdge;
|
||||
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'warehouse';
|
||||
}
|
||||
|
||||
/**
|
||||
* Свойства
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributes(),
|
||||
[
|
||||
'name',
|
||||
'addr',
|
||||
'trmn',
|
||||
'actv',
|
||||
'open'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метки свойств
|
||||
*/
|
||||
public function attributeLabels(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::attributeLabels(),
|
||||
[
|
||||
'name' => 'Название',
|
||||
'addr' => 'Адрес',
|
||||
'trmn' => 'Терминал',
|
||||
'actv' => 'Активность',
|
||||
'open' => 'Доступность'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Правила
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
parent::rules(),
|
||||
[
|
||||
[
|
||||
[
|
||||
'name',
|
||||
'addr'
|
||||
],
|
||||
'string'
|
||||
],
|
||||
[
|
||||
'actv',
|
||||
'boolean'
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись по аккаунту
|
||||
*
|
||||
* @param Account|null $account Аккаунт
|
||||
*
|
||||
* @return static|null Записанный склад
|
||||
*/
|
||||
public static function writeByAccount(Account|null $account = null): ?static
|
||||
{
|
||||
// Инициализация аккаунта
|
||||
$account = Account::initAccount($account);
|
||||
|
||||
// Инициализация склада
|
||||
$warehouse = new static;
|
||||
|
||||
// Запись параметров склада
|
||||
$warehouse->actv = true;
|
||||
|
||||
if ($warehouse->save()) {
|
||||
// Удалось записать склад в базу данных
|
||||
|
||||
// Инициализация ребра: АККАУНТ -> СКЛАД
|
||||
AccountEdgeWarehouse::writeSafe($account->readId(), $warehouse->readId(), data: ['type' => 'connected']);
|
||||
|
||||
return $warehouse;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти по аккаунту
|
||||
*
|
||||
* @param Account|null $account Аккаунт
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*
|
||||
* @return mixed Склады
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static function searchByAccount(Account|null $account = null, int $limit = 10): mixed
|
||||
{
|
||||
if ($account = Account::initAccount($account)) {
|
||||
// Инициализирован аккаунт
|
||||
|
||||
return static::searchByEdge(
|
||||
from: 'account',
|
||||
to: 'warehouse',
|
||||
edge: 'account_edge_warehouse',
|
||||
direction: 'INBOUND',
|
||||
subquery_where: [
|
||||
[
|
||||
'account_edge_warehouse._from == "' . $account->readId() . '"'
|
||||
],
|
||||
[
|
||||
'account_edge_warehouse.type == "connected"'
|
||||
]
|
||||
],
|
||||
where: 'account_edge_warehouse[0] != null',
|
||||
limit: $limit
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти по поставке
|
||||
*
|
||||
* @param Supply $supply Поставка
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*
|
||||
* @return mixed Склады
|
||||
*/
|
||||
public static function searchBySupply(Supply $supply, int $limit = 10): mixed
|
||||
{
|
||||
return static::searchByImport(Import::searchBySupply($supply, limit: 1)[0], $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти по инстанции поставки
|
||||
*
|
||||
* @param Import $import Инстанция поставки
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*
|
||||
* @return mixed Склады
|
||||
*/
|
||||
public static function searchByImport(Import $import, int $limit = 10): mixed
|
||||
{
|
||||
return self::searchByEdge(
|
||||
from: 'import',
|
||||
to: 'warehouse',
|
||||
edge: 'warehouse_edge_import',
|
||||
direction: 'OUTBOUND',
|
||||
subquery_where: [
|
||||
['warehouse_edge_import._to' => $import->readId()],
|
||||
['warehouse_edge_import.type' => 'loaded']
|
||||
],
|
||||
where: 'warehouse_edge_import[0] != null',
|
||||
limit: $limit
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация с записью
|
||||
*
|
||||
* Читает все склады привязанные к аккаунту, либо создает новый склад
|
||||
*
|
||||
* @param Account|null $account Аккаунт
|
||||
* @param int $limit — Ограничение по максимальному количеству
|
||||
*
|
||||
* @return array Склады
|
||||
*/
|
||||
public static function initWithWrite(Account|null $account = null, int $limit = 10): array
|
||||
{
|
||||
if ($account = Account::initAccount($account)) {
|
||||
// Инициализирован аккаунт
|
||||
|
||||
if ($warehouses = static::searchByAccount($account, $limit)) {
|
||||
// Найдены склады
|
||||
|
||||
return $warehouses;
|
||||
}
|
||||
|
||||
return [static::writeByAccount()];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация списка терминалов из ДеловыеЛинии для отправителя
|
||||
*
|
||||
* Актуальное (выбранное, активное) значение записывается первым
|
||||
*
|
||||
* @param array Необработанный список терминалов
|
||||
*/
|
||||
public function genListTerminalsFrom(): array
|
||||
{
|
||||
// Инициализация
|
||||
$list = [];
|
||||
|
||||
$cities = Dellin::read(limit: 9999, order: ['dellin.data.name' => 'DESC']);
|
||||
|
||||
foreach ($cities as $city) {
|
||||
// Перебор городов
|
||||
|
||||
foreach ($city->data['terminals']['terminal'] as $termial) {
|
||||
// Перебор терминалов
|
||||
|
||||
if (in_array($termial['id'], $list, true)) {
|
||||
// Если встретился дубликат (исполняется очень часто)
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Запись
|
||||
$list[$termial['id']] = $city->data['name'] . ' (' . $termial['address'] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->syncListWithSettings($list, 'trmn');
|
||||
}
|
||||
|
||||
/**
|
||||
* Синхронизация списка вариантов параметра с текущим значением из настроек
|
||||
*
|
||||
* @param array &$list Список
|
||||
* @param string $var Название параметра
|
||||
*
|
||||
* @return array Сортированный список
|
||||
*/
|
||||
protected function syncListWithSettings(array &$list, string $var): array
|
||||
{
|
||||
// Инициализация текущего значения параметра в начале массива
|
||||
if (isset($this->$var)) {
|
||||
// Параметр найден в настройках аккаунта
|
||||
|
||||
if (isset($list[$this->$var])) {
|
||||
// Найдено совпадение сохранённого параметра с полученным списком из поставок
|
||||
|
||||
// Буфер для сохранения параметра
|
||||
$buffer = $list[$this->$var];
|
||||
|
||||
// Удаление параметра
|
||||
unset($list[$this->$var]);
|
||||
|
||||
// Сохранение параметра в начале массива
|
||||
$list = [$this->$var => $buffer] + $list;
|
||||
} else {
|
||||
// Совпадение не найдено
|
||||
|
||||
// Сохранение параметра из данных аккаунта в начале массива
|
||||
$list = [$this->$var => $this->$var] + $list;
|
||||
}
|
||||
} else {
|
||||
// Параметр $var не найден в настройках аккаунта
|
||||
|
||||
// Сохранение параметра из данных аккаунта в начале массива
|
||||
$list = ['Город отправления' => 'Город отправления'] + $list;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
48
mirzaev/skillparts/system/models/WarehouseEdgeImport.php
Normal file
48
mirzaev/skillparts/system/models/WarehouseEdgeImport.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models;
|
||||
|
||||
use app\models\Account;
|
||||
|
||||
/**
|
||||
* Связь складов с инстанциями импортов
|
||||
*/
|
||||
class WarehouseEdgeImport extends Edge
|
||||
{
|
||||
/**
|
||||
* Имя коллекции
|
||||
*/
|
||||
public static function collectionName(): string
|
||||
{
|
||||
return 'warehouse_edge_import';
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по складу
|
||||
*
|
||||
* @param Warehouse $warehouse Склад
|
||||
* @param int $limit Ограничение по максимальному количеству
|
||||
*
|
||||
* @return array Связи склада и инстанций поставок
|
||||
*
|
||||
* @deprecated Бесполезно
|
||||
*/
|
||||
public static function searchByWarehouse(Warehouse $warehouse, int $limit = 1): array
|
||||
{
|
||||
return static::find()->where(['_from' => $warehouse->readId()])->limit($limit)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск по инстанции импорта
|
||||
*
|
||||
* @param Import $import Инстанция импорта
|
||||
*
|
||||
* @return array Связи склада и инстанций поставок
|
||||
*/
|
||||
public static function searchByImport(Import $import): array
|
||||
{
|
||||
return static::find()->where(['_to' => $import->readId()])->limit(1)->all();
|
||||
}
|
||||
}
|
489
mirzaev/skillparts/system/models/connection/Dellin.php
Normal file
489
mirzaev/skillparts/system/models/connection/Dellin.php
Normal file
@@ -0,0 +1,489 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models\connection;
|
||||
|
||||
use yii;
|
||||
use yii\base\Model;
|
||||
|
||||
use app\models\Dellin as DellinModel;
|
||||
use app\models\Product;
|
||||
use app\models\Account;
|
||||
use app\models\Settings;
|
||||
|
||||
use GuzzleHttp\Client as Guzzle;
|
||||
use GuzzleHttp\Exception\ClientException as GuzzleException;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
|
||||
class Dellin extends Model
|
||||
{
|
||||
/**
|
||||
* Инстанция браузера
|
||||
*/
|
||||
public static Guzzle $browser;
|
||||
|
||||
/**
|
||||
* Сессия аккаунта
|
||||
*/
|
||||
public static string $session;
|
||||
|
||||
public function __construct($config = [])
|
||||
{
|
||||
parent::__construct($config);
|
||||
|
||||
self::$browser = new Guzzle([
|
||||
'base_uri' => 'https://api.dellin.ru/'
|
||||
]);
|
||||
|
||||
self::authorization();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Поиск городов
|
||||
// *
|
||||
// * @return array|null Найденные города
|
||||
// */
|
||||
// public function searchCities(): ?array
|
||||
// {
|
||||
// $this->onReady(function () {
|
||||
// // Запрос городов
|
||||
// $request = $this->browser->post('/v2/public/kladr.json', [
|
||||
// 'json' => [
|
||||
// 'appkey' => yii::$app->params['dellin']['key'],
|
||||
|
||||
// ]
|
||||
// ])
|
||||
// });
|
||||
// }
|
||||
/**
|
||||
* Рассчет доставки (расширенный)
|
||||
*
|
||||
* Рассчет нескольких товаров идет через простое перемножение результатов доставки одного товара
|
||||
* В API всегда идет рассчет для одного товара, так было решено
|
||||
*
|
||||
* @param int $from Идентификатор терминала Dellin
|
||||
* @param int $to Идентификатор терминала Dellin
|
||||
* @param int $weight Вес (кг)
|
||||
* @param int $x Ширина (cм)
|
||||
* @param int $y Высота (cм)
|
||||
* @param int $z Длинна (cм)
|
||||
* @param int $amount Количество
|
||||
* @param Account|int|null $account Аккаунт
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @todo Загружать помимо терминалов ещё и адреса, чтобы доделать доставку малогабаритных грузов
|
||||
* Разрабраться с параметрами 0,54м * 0,39м * 0,39м (0.082134м) и 0.1 куб метр в чем разница
|
||||
*/
|
||||
public static function calcDeliveryAdvanced(int $from, int $to, int $weight, int $x, int $y, int $z, int $amount = 1, bool $avia = false, Account|int|null $account = null): array
|
||||
{
|
||||
return self::handle(function () use ($from, $to, $weight, $x, $y, $z, $amount, $avia, $account) {
|
||||
// Всё готово к работе
|
||||
|
||||
$account = Account::initAccount($account);
|
||||
|
||||
// Инициализация
|
||||
$from = DellinModel::searchByTerminalId($from, terminal_data_only: true);
|
||||
$to = DellinModel::searchByTerminalId($to, terminal_data_only: true);
|
||||
|
||||
// Значения по умолчанию, если указан 0
|
||||
if (empty($x) || $x === 0) $x = 25;
|
||||
if (empty($y) || $y === 0) $y = 40;
|
||||
if (empty($z) || $z === 0) $z = 25;
|
||||
if (empty($weight) || $weight === 0) $weight = 300;
|
||||
|
||||
// Конвертация из сантиметров в метры
|
||||
$x /= 100;
|
||||
$y /= 100;
|
||||
$z /= 100;
|
||||
|
||||
// Вычисление самой крупной стороны, так как ДеловыеЛинии имеют ограничения на все три поля и у длинны оно больше всех
|
||||
if ($x > $z && $x > $y) {
|
||||
// "X" больше всех
|
||||
|
||||
// Инициализация
|
||||
$width = $z;
|
||||
$height = $y;
|
||||
$length = $x;
|
||||
} else if ($y > $x && $y > $z) {
|
||||
// "Y" больше всех
|
||||
|
||||
// Инициализация
|
||||
$width = $x;
|
||||
$height = $z;
|
||||
$length = $y;
|
||||
} else {
|
||||
// "Z" больше всех
|
||||
|
||||
// Инициализация
|
||||
$width = $x;
|
||||
$height = $y;
|
||||
$length = $z;
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$query = [];
|
||||
|
||||
// Рассчёт типа доставки
|
||||
if (
|
||||
!$avia
|
||||
&& $weight <= 30
|
||||
&& ($length <= 0.54 && $width <= 0.39 && $height <= 0.39)
|
||||
&& $length * $width * $height <= 0.1
|
||||
) {
|
||||
// Доставка категории "small"
|
||||
|
||||
$query['delivery']['deliveryType']['type'] = 'small';
|
||||
$query['delivery']['derival']['variant'] = 'address';
|
||||
$query['delivery']['derival']['address']['search'] = $from->fullAddress;
|
||||
$query['delivery']['derival']['time']['worktimeStart'] = '08:00';
|
||||
$query['delivery']['derival']['time']['worktimeEnd'] = '20:00';
|
||||
$query['delivery']['arrival']['variant'] = 'address';
|
||||
$query['delivery']['arrival']['address']['search'] = $to->fullAddress;
|
||||
$query['delivery']['arrival']['time']['worktimeStart'] = '08:00';
|
||||
$query['delivery']['arrival']['time']['worktimeEnd'] = '20:00';
|
||||
} else {
|
||||
// Доставка категории "auto"
|
||||
|
||||
if ($avia) {
|
||||
// Рассчет для доставки по воздуху
|
||||
|
||||
// Ограничение на минимальный вес
|
||||
$weight <= 0.5 and $weight = 0.5;
|
||||
|
||||
$query['delivery']['deliveryType']['type'] = 'avia';
|
||||
} else {
|
||||
// Рассчет для доставки по земле
|
||||
|
||||
$query['delivery']['deliveryType']['type'] = 'auto';
|
||||
}
|
||||
|
||||
$query['delivery']['derival']['variant'] = 'terminal';
|
||||
$query['delivery']['derival']['terminalID'] = $from->id;
|
||||
$query['delivery']['arrival']['variant'] = 'terminal';
|
||||
$query['delivery']['arrival']['terminalID'] = $to->id;
|
||||
}
|
||||
|
||||
// Инициализация часового пояса
|
||||
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
|
||||
$timezone = $timezone[1][0];
|
||||
|
||||
// Инициализация
|
||||
$query = array_merge_recursive(
|
||||
$query,
|
||||
[
|
||||
'appkey' => yii::$app->params['dellin']['key'],
|
||||
'sessionID' => self::$session,
|
||||
'delivery' => [
|
||||
'derival' => [
|
||||
'produceDate' => (new DateTime())->setTimestamp(time() + 86400 * 3)->setTimezone(new DateTimeZone($timezone))->format('Y-m-d')
|
||||
]
|
||||
],
|
||||
'members' => [
|
||||
'requester' => [
|
||||
'role' => 'sender'
|
||||
]
|
||||
],
|
||||
'cargo' => [
|
||||
'quantity' => 1,
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'length' => $length,
|
||||
'totalVolume' => $width * $height * $length,
|
||||
'totalWeight' => $weight,
|
||||
'oversizedWeight' => $weight,
|
||||
'oversizedVolume' => $width * $height * $length
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// Запрос
|
||||
$request = self::$browser->post('/v2/calculator.json', [
|
||||
'json' => $query
|
||||
]);
|
||||
|
||||
if ($request->getStatusCode() === 200) {
|
||||
// Запрос прошел успешно
|
||||
|
||||
// Инициализация
|
||||
$response = json_decode((string) $request->getBody(), true);
|
||||
|
||||
if ($response['metadata']['status'] === 200) {
|
||||
// Со стороны ДеловыеЛинии ошибок нет
|
||||
|
||||
$response['data']['price'] = [
|
||||
'one' => $response['data']['price'],
|
||||
'all' => $response['data']['price'] * $amount
|
||||
];
|
||||
|
||||
return $response['data'];
|
||||
}
|
||||
|
||||
throw new Exception('На стороне сервера ДеловыеЛинии какие-то проблемы, либо отправлен неверный запрос', 500);
|
||||
}
|
||||
|
||||
throw new Exception('Не удалось запросить рассчёт доставки у ДеловыеЛинии', 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Рассчет доставки
|
||||
*
|
||||
* @param string $from Идентификатор терминала Dellin
|
||||
* @param string $to Идентификатор терминала Dellin
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static function calcDelivery(string $from, string $to): array
|
||||
{
|
||||
return self::handle(function () use ($from, $to) {
|
||||
// Всё готово к работе
|
||||
|
||||
// Запрос
|
||||
$request = self::$browser->post('/v1/micro_calc.json', [
|
||||
'json' => [
|
||||
'appkey' => yii::$app->params['dellin']['key'],
|
||||
'sessionID' => self::$session,
|
||||
'derival' => [
|
||||
'city' => $from
|
||||
],
|
||||
'arrival' => [
|
||||
'city' => $to
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
if ($request->getStatusCode() === 200) {
|
||||
// Запрос прошел успешно
|
||||
|
||||
// Инициализация
|
||||
$response = json_decode((string) $request->getBody(), true);
|
||||
|
||||
if ($response['metadata']['status'] === 200) {
|
||||
// Со стороны ДеловыеЛинии ошибок нет
|
||||
|
||||
return $response['data'];
|
||||
}
|
||||
|
||||
throw new Exception('На стороне сервера ДеловыеЛинии какие-то проблемы, либо отправлен неверный запрос', 500);
|
||||
}
|
||||
|
||||
throw new Exception('Не удалось запросить рассчёт доставки у ДеловыеЛинии', 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Импорт терминалов
|
||||
*
|
||||
* @return array|null Сохранённые терминалы
|
||||
*/
|
||||
public static function importTerminals(Account|int|null $account = null): ?int
|
||||
{
|
||||
return self::handle(function () use ($account) {
|
||||
// Всё готово к работе
|
||||
|
||||
if (is_null($account)) {
|
||||
// Данные аккаунта не переданы
|
||||
|
||||
if (isset(yii::$app->user)) {
|
||||
if (yii::$app->user->isGuest) {
|
||||
// Аккаунт не аутентифицирован
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
// Аккаунт аутентифицирован
|
||||
|
||||
// Инициализация
|
||||
$account = yii::$app->user->identity;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (is_int($account)) {
|
||||
// Передан идентификатор (_key) аккаунта (подразумевается)
|
||||
|
||||
// Инициализация (поиск в базе данных)
|
||||
if (!$account = Account::searchById(Account::collectionName() . "/$account")) {
|
||||
// Не удалось инициализировать аккаунт
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Запрос ссылки на файл с городами, возвращает ['hash' => string, 'url' => string]
|
||||
$request = self::$browser->post('/v3/public/terminals.json', [
|
||||
'json' => [
|
||||
'appkey' => yii::$app->params['dellin']['key'],
|
||||
]
|
||||
]);
|
||||
|
||||
if ($request->getStatusCode() === 200) {
|
||||
// Запрос прошел успешно
|
||||
|
||||
// Инициализация часового пояса
|
||||
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
|
||||
$timezone = $timezone[1][0];
|
||||
|
||||
// Инициализация параметров
|
||||
$response = json_decode((string) $request->getBody(), true);
|
||||
$dir = YII_PATH_PUBLIC . '/../assets/import/' . (new DateTime('now', new DateTimeZone($timezone)))->format('Y-m-d') . '/dellin/terminals/' . (yii::$app->user->identity->_key ?? 'system') . '/';
|
||||
$amount = 0;
|
||||
|
||||
if (!file_exists($dir)) {
|
||||
// Директории не существует
|
||||
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
|
||||
$request = self::$browser->get($response['url'], [
|
||||
'sink' => $file = $dir . time() . '.json'
|
||||
]);
|
||||
|
||||
// Инициализация
|
||||
$terminals = json_decode(fread(fopen($file, "r"), filesize($file)), true);
|
||||
|
||||
foreach ($terminals['city'] as $terminal) {
|
||||
// Перебор городов
|
||||
|
||||
if ($model = DellinModel::searchByCityId($terminal['id'])) {
|
||||
// Удалось найти город в базе данных
|
||||
|
||||
$after_import_log = function () use ($model): void {
|
||||
// Запись в журнал
|
||||
$model->journal('update');
|
||||
|
||||
if (yii::$app->getRequest()->isConsoleRequest) {
|
||||
// Вызов из терминала
|
||||
|
||||
echo 'Удалось перезаписать терминалы города: ' . ($model->data['name'] ?? 'Неизвестно') . PHP_EOL;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Не удалось найти город в базе данных
|
||||
|
||||
$model = new DellinModel();
|
||||
|
||||
$after_import_log = function () use ($model): void {
|
||||
if (yii::$app->getRequest()->isConsoleRequest) {
|
||||
// Вызов из терминала
|
||||
|
||||
echo 'Удалось записать терминалы города: ' . ($model->data['name'] ?? 'Неизвестно') . PHP_EOL;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Запись
|
||||
$model->data = $terminal;
|
||||
|
||||
// Отправка в базу данных
|
||||
if ($model->save()) {
|
||||
// Удалось сохранить в базе данных
|
||||
|
||||
// Запись в журнал
|
||||
$after_import_log();
|
||||
|
||||
// Постинкрементация счётчика
|
||||
$amount++;
|
||||
|
||||
continue;
|
||||
} else {
|
||||
// Не удалось сохранить в базе данных
|
||||
|
||||
throw new Exception('Не удалось сохранить терминалы города "' . ($model->data['name'] ?? 'Неизвестно') . '" в базу данных', 500);
|
||||
}
|
||||
}
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Аутентификация и авторизация
|
||||
*/
|
||||
protected static function authorization(): bool
|
||||
{
|
||||
// Аутентификация и авторизация
|
||||
$request = self::browser()->post('/v3/auth/login.json', [
|
||||
'json' => [
|
||||
'appkey' => yii::$app->params['dellin']['key'],
|
||||
'login' => yii::$app->params['dellin']['nickname'],
|
||||
'password' => yii::$app->params['dellin']['password']
|
||||
]
|
||||
]);
|
||||
|
||||
if ($request->getStatusCode() === 200) {
|
||||
// Запрос прошел успешно
|
||||
|
||||
// Инициализация
|
||||
$response = json_decode((string) $request->getBody(), true);
|
||||
|
||||
if ($response['metadata']['status'] === 200) {
|
||||
// Аутентификация и авторизация пройдены успешно
|
||||
|
||||
// Запись сессии
|
||||
self::$session = $response['data']['sessionID'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $response['metadata']['status']);
|
||||
}
|
||||
|
||||
throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $request->getStatusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация и выполнение
|
||||
*
|
||||
* @param callable|null $function Код к выполнению
|
||||
* @param [mixed] ...$vars Параметры к нему
|
||||
*
|
||||
* @return mixed Возврат из функции
|
||||
*/
|
||||
protected static function handle(callable $function = null, mixed ...$vars): mixed
|
||||
{
|
||||
try {
|
||||
if (self::browser() instanceof Guzzle) {
|
||||
// Браузер инициализирован
|
||||
|
||||
if (self::authorization() && isset(self::$session)) {
|
||||
// Аутентифицирован и авторизован
|
||||
|
||||
return $function(...$vars);
|
||||
} else {
|
||||
throw new Exception('Аккаунт не авторизирован', 401);
|
||||
}
|
||||
} else {
|
||||
throw new Exception('Браузер не инициализирован', 500);
|
||||
}
|
||||
} catch (GuzzleException $e) {
|
||||
throw new Exception($e->getResponse()->getBody()->getContents() ?? 'Не удалось инициализировать инстанцию для работы с API ДеловыеЛинии', 500, $e->getPrevious());
|
||||
} catch (Exception $e) {
|
||||
throw new Exception($e->getMessage() ?? 'Не удалось инициализировать инстанцию для работы с API ДеловыеЛинии', 500, $e->getPrevious());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Чтение или инициализация браузера
|
||||
*
|
||||
* @return Guzzle Инстанция
|
||||
*/
|
||||
protected static function browser(): Guzzle
|
||||
{
|
||||
return self::$browser ?? self::$browser = new Guzzle([
|
||||
'base_uri' => 'https://api.dellin.ru/'
|
||||
]);
|
||||
}
|
||||
}
|
59
mirzaev/skillparts/system/models/helpers/JsManager.php
Normal file
59
mirzaev/skillparts/system/models/helpers/JsManager.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\models\helpers;
|
||||
|
||||
use yii;
|
||||
use yii\base\Model;
|
||||
use yii\web\View;
|
||||
|
||||
class JsManager extends Model
|
||||
{
|
||||
/**
|
||||
* Сгенерировать <script> элементы из данных AssetManager
|
||||
*
|
||||
* Создавалось под виджет ActiveForm и только на нём тестировалось
|
||||
*
|
||||
* @param View $view Представление
|
||||
* @param array $targets Названия виджетов (например: "yii\widgets\ActiveFormAsset")
|
||||
*
|
||||
* @return string HTML-код
|
||||
*/
|
||||
public static function include(View $view, array $targets): string
|
||||
{
|
||||
// Инициализация
|
||||
$buffer = '';
|
||||
|
||||
foreach ($targets as $target) {
|
||||
// Перебор целей для генерации
|
||||
|
||||
// Инициализация
|
||||
$depends = $view->assetBundles[$target]->depends;
|
||||
|
||||
if (count($depends) > 0) {
|
||||
// Найдены зависимости
|
||||
|
||||
// Рекурсивный вызов
|
||||
self::include($view, $depends);
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$files = $view->assetBundles[$target]->js;
|
||||
|
||||
foreach ($files as $file) {
|
||||
// Перебор файлов цели для генерации
|
||||
|
||||
// Инициализация публичного пути к файлу
|
||||
$path = $view->assetBundles[$target]->baseUrl . '/' . $file;
|
||||
|
||||
// Генерация
|
||||
$buffer .= <<<HTML
|
||||
<script src="$path" defer></script>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
@@ -4,35 +4,36 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\models\traits;
|
||||
|
||||
use yii;
|
||||
|
||||
use exception;
|
||||
use ArangoDBClient\Document;
|
||||
|
||||
trait SearchByEdge
|
||||
{
|
||||
/**
|
||||
* Поиск через связи рёбрами с аккаунтом
|
||||
*
|
||||
* @param string $id Идентификатор пользователя
|
||||
* @param int $limit Количество
|
||||
* @param int $offset Сдвиг
|
||||
* @param string $sort Сортировка
|
||||
* Аргумент $asArray и его реализацию пришлось добавить чтобы не переписывать кучу кода
|
||||
*/
|
||||
public static function searchByEdge(
|
||||
string $from,
|
||||
string $to,
|
||||
string|null $edge = null,
|
||||
string $direction = 'ANY',
|
||||
int|null $limit = 10,
|
||||
int|null $offset = 0,
|
||||
array $sort = ['ASC'],
|
||||
array|string|null $sort = null,
|
||||
string|array $subquery_where = [],
|
||||
string|array $subquery_select = null,
|
||||
array $foreach = [],
|
||||
string|array $where = [],
|
||||
string $direction = 'ANY',
|
||||
array|null $let = [],
|
||||
string|array $select = null,
|
||||
callable|null $handle = null,
|
||||
array $params = []
|
||||
array|null $filterStart = null,
|
||||
array $params = [],
|
||||
bool $asArray = true,
|
||||
bool $debug = false,
|
||||
bool $aql = false,
|
||||
bool $count = false
|
||||
): mixed {
|
||||
$subquery = static::find()
|
||||
->params($params)
|
||||
@@ -41,7 +42,8 @@ trait SearchByEdge
|
||||
->in($edge ?? $from . '_edge_' . $to)
|
||||
->where($subquery_where);
|
||||
|
||||
$subquery = $subquery->select($edge ?? $from . '_edge_' . $to)
|
||||
$subquery = $subquery
|
||||
->select($subquery_select ?? $edge ?? $from . '_edge_' . $to)
|
||||
->createCommand();
|
||||
|
||||
$query = static::find()
|
||||
@@ -57,27 +59,68 @@ trait SearchByEdge
|
||||
$query->let(...$let);
|
||||
}
|
||||
|
||||
// Запрос
|
||||
if (!empty($filterStart)) {
|
||||
// Переданы дополнительные условия фильтрации
|
||||
|
||||
$query->filter($filterStart, 'START_SENSETIVE');
|
||||
}
|
||||
|
||||
// Инициализация
|
||||
$request = $query
|
||||
->foreach($foreach)
|
||||
->where($where)
|
||||
->limit($limit)
|
||||
->select($select ?? $to);
|
||||
|
||||
if (isset($handle)) {
|
||||
// Режим вывода строки запроса
|
||||
if ($aql) {
|
||||
// Запрошена проверка
|
||||
|
||||
return (string) $request->createCommand();
|
||||
}
|
||||
|
||||
// Режим проверки
|
||||
if ($debug) {
|
||||
// Запрошена проверка
|
||||
|
||||
var_dump((string) $request->createCommand());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Запрос
|
||||
if ($count) {
|
||||
// Запрошен подсчет
|
||||
|
||||
return $query->count();
|
||||
} else if (isset($handle)) {
|
||||
// Передана функция для постобработки
|
||||
|
||||
return $handle($request);
|
||||
} else if (isset($select)) {
|
||||
// Указан выбор свойств
|
||||
|
||||
$response = $request->createCommand()->execute()->getAll();
|
||||
|
||||
foreach ($response as &$attribute) {
|
||||
// Приведение всех свойств в массив и очистка от лишних данных
|
||||
if ($asArray) {
|
||||
// Передан параметр указывающий на необходимость возврата как объекта
|
||||
|
||||
$attribute = $attribute->getAll();
|
||||
// Очистка
|
||||
foreach ($response as &$attribute) {
|
||||
// Приведение всех свойств в массив и очистка от лишних данных
|
||||
|
||||
if ($attribute instanceof Document) {
|
||||
// Получена инстанция документа ArangoDB
|
||||
|
||||
$attribute = $attribute->getAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
} else {
|
||||
// Иначе просто запросить для ActiveQuery
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
use yii\helpers\Html;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
use yii\widgets\Pjax;
|
||||
|
||||
use app\models\AccountForm;
|
||||
|
||||
@@ -16,34 +17,107 @@ use app\models\AccountForm;
|
||||
<div class="row">
|
||||
<div class="mx-auto">
|
||||
<?php
|
||||
|
||||
// Инициализация идентификатора формы
|
||||
if ($panel ?? false) {
|
||||
// Генерация документа во всплывающем меню
|
||||
|
||||
// Инициализация параметров
|
||||
$form_id = 'form_account_panel';
|
||||
$target = 'panel';
|
||||
} else {
|
||||
// Генерация документа в основном блоке страницы
|
||||
|
||||
// Инициализация параметров
|
||||
$form_id = 'form_account';
|
||||
$target = 'main';
|
||||
}
|
||||
|
||||
$form = ActiveForm::begin([
|
||||
'id' => 'form_account',
|
||||
'id' => $form_id,
|
||||
'action' => false,
|
||||
'fieldConfig' => [
|
||||
'template' => '{label}{input}{error}',
|
||||
'options' => ['class' => '']
|
||||
'options' => [
|
||||
'class' => ''
|
||||
]
|
||||
],
|
||||
'options' => [
|
||||
'class' => '',
|
||||
'class' => 'form_account',
|
||||
'onsubmit' => 'return false;'
|
||||
]
|
||||
],
|
||||
'enableClientValidation' => false,
|
||||
'enableAjaxValidation' => true
|
||||
]);
|
||||
|
||||
$model = $model ?? new AccountForm;
|
||||
?>
|
||||
|
||||
<?= $form->field($model, 'mail', ['enableLabel' => false, 'options' => ['class' => 'mb-2']])->textInput(['autofocus' => true, 'placeholder' => $model->getAttributeLabel('mail')]) ?>
|
||||
<?= $form->field($model, 'pswd', ['enableLabel' => false])->passwordInput(['placeholder' => $model->getAttributeLabel('pswd')]) ?>
|
||||
<?php if ($registration ?? false) : ?>
|
||||
<?php if ($panel ?? false) : ?>
|
||||
<h5 class="mb-4 text-center">Регистрация</h5>
|
||||
<?php else : ?>
|
||||
<h3 class="mb-4 text-center">Регистрация</h3>
|
||||
<?php endif ?>
|
||||
<?php else : ?>
|
||||
<?php if ($panel ?? false) : ?>
|
||||
<h5 class="mb-4 text-center">Аутентификация</h5>
|
||||
<?php else : ?>
|
||||
<h3 class="mb-4 text-center">Аутентификация</h3>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $form->field($model, 'mail', ['enableLabel' => false, 'options' => ['class' => 'mb-2'], 'inputOptions' => ['class' => 'form-control button_clean'], 'errorOptions' => ['class' => 'help-block help-block-error px-2 small']])->textInput(['autofocus' => true, 'placeholder' => $model->getAttributeLabel('mail')]) ?>
|
||||
<?= $form->field($model, 'pswd', ['enableLabel' => false, 'inputOptions' => ['class' => 'form-control button_clean'], 'errorOptions' => ['class' => 'help-block help-block-error px-2 small']])->passwordInput(['placeholder' => $model->getAttributeLabel('pswd')]) ?>
|
||||
|
||||
<div class="d-flex mb-2 mt-3">
|
||||
<?= Html::submitButton('Войти', ['name' => 'submitAuthentication', 'onclick' => 'authentication(this.parentElement.parentElement);', 'class' => 'flex-grow-1 mr-2 btn btn-primary button_clean']) ?>
|
||||
<?= Html::submitButton('Войти', ['name' => 'submitAuthentication', 'onclick' => 'authentication(this.parentElement.parentElement, \'' . $target . '\');', 'class' => 'flex-grow-1 mr-2 btn btn-primary button_clean']) ?>
|
||||
<?= $form->field($model, 'auto', ['checkboxTemplate' => '<div class="checkbox button_clean">{beginLabel}' .
|
||||
Html::submitButton('{labelTitle}', ['name' => 'submit', 'data-toggle' => 'button', 'class' => 'w-100 btn btn-primary button_clean', 'aria-pressed' => 'false']) .
|
||||
Html::submitButton('{labelTitle}', ['name' => 'submit', 'data-toggle' => 'button', 'class' => 'w-100 btn btn-primary button_clean', 'aria-pressed' => 'false', 'onclick' => 'return authentication_auto_button_status_switch(this);']) .
|
||||
'{endLabel}</div>'])->checkbox()->label($model->getAttributeLabel('auto'), ['class' => 'w-100 m-0']) ?>
|
||||
</div>
|
||||
<?= Html::submitButton('Регистрация', ['name' => 'submitRegistration', 'onclick' => 'registration(this.parentElement);', 'class' => 'col-12 ml-auto btn btn-success btn-sm button_clean']) ?>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
<?= Html::submitButton('Регистрация', ['name' => 'submitRegistration', 'onclick' => 'return registration_start(this.parentElement, \'' . $target . '\');', 'class' => 'col-12 ml-auto btn btn-success btn-sm button_clean']) ?>
|
||||
<small class="d-flex mt-2"><a class="mx-auto text-dark" type="button" onclick="restore(this.parentElement.parentElement)">Восстановить пароль</a></small>
|
||||
|
||||
<?php
|
||||
|
||||
ActiveForm::end();
|
||||
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($registration ?? false) : ?>
|
||||
<script async="false">
|
||||
// Инициализация формы
|
||||
let form = document.getElementById('<?= $form_id ?>');
|
||||
|
||||
// Запуск программы регистрации
|
||||
registration_start(form, 'panel');
|
||||
|
||||
if (document.readyState === "complete") {
|
||||
// Документ загружен
|
||||
|
||||
// Обработчик события инициализации
|
||||
$(form).on('afterInit', function(e) {
|
||||
|
||||
// Запуск программы регистрации
|
||||
registration_start(form, 'panel');
|
||||
});
|
||||
} else {
|
||||
// Документ не загружен
|
||||
|
||||
// Обработчик события загрузки документа
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Обработчик события инициализации
|
||||
$(form).on('afterInit', function(e) {
|
||||
|
||||
// Запуск программы регистрации
|
||||
registration_start(form, 'panel');
|
||||
});
|
||||
}, false);
|
||||
}
|
||||
</script>
|
||||
<?php endif ?>
|
||||
|
61
mirzaev/skillparts/system/views/account/list.php
Normal file
61
mirzaev/skillparts/system/views/account/list.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use app\models\Product;
|
||||
use app\models\Account;
|
||||
use app\models\Settings;
|
||||
|
||||
// Инициализация счетчика аккаунтов
|
||||
$i = $amount * ($page - 1);
|
||||
|
||||
// Инициализация часового пояса
|
||||
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
|
||||
$timezone = $timezone[1][0];
|
||||
?>
|
||||
|
||||
<?php if ($page > 1) : ?>
|
||||
<div class="dropdown-divider mb-3"></div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php foreach ($accounts ?? [] as $account) : ?>
|
||||
<?php
|
||||
foreach ($account->jrnl ?? [] as $jrnl) {
|
||||
// Перебор записей в журнале
|
||||
|
||||
if ($jrnl['action'] === 'create') {
|
||||
// Найдена дата создания
|
||||
|
||||
// Инициализация даты
|
||||
$create = (new DateTime())->setTimestamp($jrnl['date'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?? 'Неизвестно';
|
||||
|
||||
// Выход из цикла
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="pr-0 col-auto"><?= ++$i ?>.</div>
|
||||
<div class="pr-0 col overflow-hidden" title="ФИО">
|
||||
<?= $account->name ?? 'Неизвестно' ?>
|
||||
</div>
|
||||
<div class="my-auto pr-0 col-auto" title="Псевдоним">
|
||||
<?= $account->indx ?? '' ?>
|
||||
</div>
|
||||
<div class="my-auto pr-0 col-auto" title="Тип аккаунта">
|
||||
<?= $account->agnt ? 'Поставщик' : 'Покупатель' ?>
|
||||
</div>
|
||||
<div class="mr-3 my-auto pr-0 col-2" title="Уровень авторизации">
|
||||
<?= $account->type() ?>
|
||||
</div>
|
||||
<div class="my-auto pr-0 col-auto text-right" title="Дата регистрации">
|
||||
<?= $create ?? 'Неизвестно' ?>
|
||||
</div>
|
||||
<a class="my-auto col-auto fas fa-trash-alt text-dark" type="button" onclick="page_profile_supplies_delete()"></a>
|
||||
</div>
|
||||
|
||||
<?php if ($i < count($accounts) + $amount * ($page - 1)) : ?>
|
||||
<div class="dropdown-divider mb-3"></div>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
@@ -4,27 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
use yii;
|
||||
|
||||
if (!yii::$app->user->isGuest) {
|
||||
// $popup = yii::$app->controller->renderPartial('/notification/panel');
|
||||
|
||||
// echo <<<HTML
|
||||
// <a id="notification_button" class="text-dark d-flex h-100 mr-2" title="Уведомления" role="button" data-toggle="dropdown" data-offset="-100%p + 100%" onclick="return notification_stream();">
|
||||
// <i class="fas fa-bell my-auto mx-2"></i>
|
||||
// </a>
|
||||
// <div id="notification_button_panel" class="dropdown-menu p-2" aria-labelledby="notification_button">
|
||||
// $popup
|
||||
// </div>
|
||||
// HTML;
|
||||
}
|
||||
?>
|
||||
<?=$notifications_button?>
|
||||
<?=$notifications_panel?>
|
||||
<a class="text-dark my-auto mr-2" title="Корзина" href="/cart" role="button" onclick="return page_cart();"><i class="fas fa-shopping-cart mx-2"></i></a>
|
||||
|
||||
<?= $notifications_panel ?>
|
||||
<?= $notifications_button ?>
|
||||
<?= $cart_button ?>
|
||||
<a class="text-dark my-auto mr-2" title="Заказы" href="/orders" role="button" onclick="return page_orders();"><i class="fas fa-list mx-2"></i></a>
|
||||
<div class="btn-group my-auto">
|
||||
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
|
||||
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>
|
||||
<div id="profile_button_panel" class="dropdown-menu dropdown-menu-right py-1" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')">
|
||||
<a class="dropdown-item button_white text-dark" onclick="deauthentication()">Выход (<?= yii::$app->user->identity->mail ?>)</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn m-0 px-0 text-dark button_clean button_underline" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();"><b><?= yii::$app->user->identity->mail ?></b></a>
|
||||
<button id="menu_auth_panel_button" class="pr-0 btn button_clean button_clean_full" type="button" onclick="return deauthentication();" title="Выход"><i class="fas fa-sign-out-alt"></i></button>
|
||||
<!-- <div id="menu_auth_panel" class="py-1 text-center d-none">
|
||||
<a class="py-1 px-3 d-block button_white text-dark" onclick="return deauthentication();"><b>Выход</b></a>
|
||||
</div> -->
|
||||
</div>
|
||||
|
@@ -3,16 +3,18 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use yii;
|
||||
use app\models\helpers\JsManager;
|
||||
|
||||
?>
|
||||
|
||||
<a class="text-dark my-auto mr-2" title="Корзина" href="/cart" role="button" onclick="return page_cart();"><i class="fas fa-shopping-cart mx-2"></i></a>
|
||||
<a class="text-dark my-auto mr-2" title="Заказы" href="/orders" role="button" onclick="return page_orders();"><i class="fas fa-list mx-2"></i></a>
|
||||
<div class="btn-group my-auto">
|
||||
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
|
||||
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>
|
||||
<div id="profile_button_panel" class="dropdown-menu dropdown-menu-long dropdown-menu-right p-3" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')">
|
||||
<h5 class="mb-3 text-center">Аутентификация</h5>
|
||||
<?= yii::$app->controller->renderPartial('/account/index', compact('model')) ?>
|
||||
<!-- <a class="dropdown-item-text text-center px-0 py-2" href="#"><small>Восстановление пароля</small></a> -->
|
||||
<button id="menu_auth_panel_button" class="pr-0 btn button_clean button_clean_full" type="button" onmouseover="return menu_auth_panel_show(document.getElementById('menu_auth_panel'));" onclick="return menu_auth_panel_show(document.getElementById('menu_auth_panel'));"><i class="fas fa-caret-down"></i></button>
|
||||
<div id="menu_auth_panel" class="p-3 d-none">
|
||||
<?= yii::$app->controller->renderPartial('/account/index', compact('model') + ['panel' => true]) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= JsManager::include($this, ['yii\widgets\ActiveFormAsset']);?>
|
||||
|
11
mirzaev/skillparts/system/views/account/verify.php
Normal file
11
mirzaev/skillparts/system/views/account/verify.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="container d-flex flex-column">
|
||||
<div class="my-auto">
|
||||
<div class="col-md-10 col-lg-8 mx-auto p-5 alert alert_white d-flex flex-column">
|
||||
<h4 class="text-center"><b>Подтвердите аккаунт</b></h4>
|
||||
<p class="text-center mb-5">Мы выслали вам письмо с паролем и ссылкой на активацию</p>
|
||||
|
||||
<a class="btn button_blue button_clean mx-auto" type="button" onclick="return verify_resend();">Повторить</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/verify.js" defer></script>
|
92
mirzaev/skillparts/system/views/buyers/index.php
Normal file
92
mirzaev/skillparts/system/views/buyers/index.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use yii\helpers\Html;
|
||||
|
||||
use app\assets\AppAsset;
|
||||
|
||||
AppAsset::register($this);
|
||||
|
||||
?>
|
||||
|
||||
<?php $this->beginPage() ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= yii::$app->language ?>">
|
||||
|
||||
<head>
|
||||
<meta charset="<?= yii::$app->charset ?>">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="/css/pages/buyers.css" rel="stylesheet">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/favicons/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/favicons/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/favicons/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/favicons/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/favicons/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/favicons/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/favicons/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/favicons/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/favicons/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicons/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png">
|
||||
<link rel="manifest" href="/favicons/manifest.json">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="/favicons/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
|
||||
<?php $this->registerCsrfMetaTags() ?>
|
||||
|
||||
<title><?= Html::encode($this->title ?? 'Покупателям | SkillParts') ?></title>
|
||||
|
||||
<?php $this->head() ?>
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column">
|
||||
<?php $this->beginBody() ?>
|
||||
|
||||
<header class="container pt-2 mt-1 mb-2 mb-sm-4">
|
||||
<div class="row row h-100">
|
||||
<div id="logo" class="col-3 col-md-4 h-100">
|
||||
<a class="py-2 h-100 d-inline-flex" title="SkillParts" href="/">
|
||||
<img class="img-fluid" src="/img/logos/skillparts.svg" alt="SkillParts">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="page_buyers" class="container pb-5 flex-grow-1 d-flex justify-content-center">
|
||||
<article class="col my-auto p-3 px-0 rounded overflow-hidden">
|
||||
<h1 class="py-3 mt-2 mb-5 text-center"><b>Что в нас ценят?</b></h1>
|
||||
<div class="row mb-5 pb-3">
|
||||
<div class="col-4">
|
||||
<img class="px-5 img-fluid" src="/img/icons/track.png" title="Подробное отслеживание" />
|
||||
<h5 class="text-center"><b>Подробное отслеживание</b></h5>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<img class="px-5 img-fluid" src="/img/icons/truck.png" title="Бесплатное экспедирование" />
|
||||
<h5 class="text-center"><b>Бесплатное экспедирование</b></h5>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<img class="px-5 img-fluid" src="/img/icons/assortiment.png" title="Отлаженная сеть поставщиков" />
|
||||
<h5 class="text-center"><b>Отлаженная сеть поставщиков</b></h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-3 pb-3 justify-content-center">
|
||||
<a class="mr-4 text-center text-white btn button_blue button_clean" href="/registration">Зарегистрироваться</a>
|
||||
<a class="text-center text-white btn button_blue button_clean" href="/offer">Оферта</a>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<?php $this->endBody() ?>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<?php $this->endPage() ?>
|
18
mirzaev/skillparts/system/views/cart/button.php
Normal file
18
mirzaev/skillparts/system/views/cart/button.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<a id="cart_button" class="mr-2 h-100 text-dark d-flex" title="Корзина" href="/cart" onclick="return page_cart();">
|
||||
<?php
|
||||
if (empty($cart_amount) || $cart_amount < 1) {
|
||||
// Новые уведомления не найдены
|
||||
|
||||
echo <<<HTML
|
||||
<i class="mx-2 my-auto fas fa-shopping-cart"></i>
|
||||
HTML;
|
||||
} else {
|
||||
// Новые уведомления найдены
|
||||
|
||||
echo <<<HTML
|
||||
<small class="ml-2 mr-1 my-auto"><b>$cart_amount</b></small>
|
||||
<i class="mr-2 my-auto fas fa-shopping-cart cart_button_active"></i>
|
||||
HTML;
|
||||
}
|
||||
?>
|
||||
</a>
|
@@ -1,18 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use yii;
|
||||
use yii\bootstrap\ActiveForm;
|
||||
|
||||
use app\models\connection\Dellin;
|
||||
use app\models\Supply;
|
||||
use DateTime;
|
||||
|
||||
?>
|
||||
|
||||
<link href="/css/pages/cart.css" rel="stylesheet">
|
||||
|
||||
<div id="page_cart" class="container mb-auto py-3">
|
||||
<article class="py-3 px-4 rounded">
|
||||
<article class="p-4 rounded">
|
||||
<h4 class="ml-4 mt-2 mb-4"><i class="fas fa-shopping-cart mr-2"></i>Корзина</h4>
|
||||
<div class="col mb-4 list rounded overflow-hidden">
|
||||
<div class="row py-2">
|
||||
<div class="pl-3 mr-1">
|
||||
<input id="checkbox_cart_all" type="checkbox" onchange="return cart_list_checkbox(this);"/>
|
||||
<input id="checkbox_cart_all" type="checkbox" onchange="return cart_list_checkbox(this);" />
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<span>Производитель</span>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<span>Артикул</span>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<span>Описание</span>
|
||||
<div class="col-2">
|
||||
<span>Поставщик</span>
|
||||
</div>
|
||||
<div class="col-1 ml-auto px-0 text-center">
|
||||
<span>Количество</span>
|
||||
@@ -25,34 +41,105 @@
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
if (isset($supplies) && !empty($supplies)) {
|
||||
foreach ($supplies as $supply) {
|
||||
echo <<<HTML
|
||||
<div class="row py-2 cart_list_target">
|
||||
<div class="pl-3 mr-1">
|
||||
<input id="cart_list_checkbox_$supply->catn" type="checkbox" onchange="return cart_list_checkbox(this);"/>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
$supply->catn
|
||||
</div>
|
||||
<div class="col-4">
|
||||
$supply->desc
|
||||
</div>
|
||||
<div class="col-1 ml-auto">
|
||||
<input id="cart_list_amnt_$supply->catn" class="form-control text-center" type="text" value="$supply->amnt" onchange="return cart_list_amount_update('$supply->catn', this)" aria-invalid="false">
|
||||
</div>
|
||||
<div class="col-2 text-right">
|
||||
$supply->time
|
||||
</div>
|
||||
<div class="col-2 mr-3 text-right">
|
||||
$supply->cost
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
if (!empty($data['supplies'])) {
|
||||
// Найдены цели для заказа
|
||||
|
||||
// Инициализация списка поставок
|
||||
$targets = [];
|
||||
|
||||
foreach ($data['supplies'] as $prod => $list) {
|
||||
// Перебор поставщиков
|
||||
|
||||
foreach ($list as $catn => $deliveries) {
|
||||
// Перебор поставок
|
||||
|
||||
foreach ($deliveries as $delivery => $supply) {
|
||||
// Перебор типов доставки
|
||||
|
||||
// Инициализация комментария
|
||||
$comment = $supply['edge']['comm'] ?? 'Комментарий к заказу';
|
||||
|
||||
// Инициализация доставки
|
||||
if (empty($supply['delivery'])) {
|
||||
// Не удалось рассчитать доставку
|
||||
|
||||
// Инициализация времени
|
||||
$days = '?';
|
||||
} else {
|
||||
// Удалось рассчитать доставку
|
||||
|
||||
// Инициализация даты отправки
|
||||
try {
|
||||
// Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)
|
||||
|
||||
$delivery_send_date = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['arrivalToOspSender'])->getTimestamp();
|
||||
} catch (Throwable $e) {
|
||||
// Взять данные из "pickup" (Дата передачи груза на адресе отправителя)
|
||||
|
||||
$delivery_send_date = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['pickup'])->getTimestamp();
|
||||
}
|
||||
|
||||
// Инициализация времени доставки
|
||||
try {
|
||||
// Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале)
|
||||
|
||||
// Оставлено на всякий случай для дальнейших разбирательств
|
||||
|
||||
$delivery_converted = DateTime::createFromFormat('Y-m-d H:i:s', $supply['delivery']['orderDates']['giveoutFromOspReceiver'])->getTimestamp();
|
||||
} catch (Throwable $e) {
|
||||
// Инициализация даты отправки
|
||||
|
||||
// Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель)
|
||||
$delivery_converted = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['arrivalToOspReceiver'])->getTimestamp();
|
||||
}
|
||||
$days = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24) + 1;
|
||||
}
|
||||
|
||||
// Инициализация иконки
|
||||
$icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck';
|
||||
|
||||
// Генерация HTML
|
||||
echo <<<HTML
|
||||
<div class="row py-2 cart_list_target">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="pl-3 my-auto mr-1">
|
||||
<input id="cart_list_checkbox_{$prod}_{$catn}_auto" type="checkbox" onchange="return cart_list_checkbox(this);"/>
|
||||
</div>
|
||||
<div class="col-2 my-auto">
|
||||
$prod
|
||||
</div>
|
||||
<div class="col-2 my-auto">
|
||||
$catn
|
||||
</div>
|
||||
<div class="col-2 my-auto">
|
||||
{$supply['account']['indx']}
|
||||
</div>
|
||||
<div class="col-1 my-auto ml-auto">
|
||||
<input id="cart_list_amnt_{$prod}_{$catn}_auto" class="form-control text-center" type="text" value="{$supply['amount']}" onchange="return cart_list_amount_update('$prod', '$catn', 'auto', this)" aria-invalid="false">
|
||||
</div>
|
||||
<div class="col-2 my-auto text-right">
|
||||
<p title="Ориентировочно"><i class="mr-1 fas $icon"></i> <b>~</b>$days дн</p>
|
||||
</div>
|
||||
<div class="col-2 my-auto mr-3 text-right">
|
||||
{$supply['supply']->cost} {$supply['currency']}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="row mb-1">
|
||||
<div class="col-12">
|
||||
<p id="cart_list_comment_{$prod}_{$catn}_auto" class="mt-0 ml-0 text-break pointer-event" role="button" onclick="return cart_list_comment_edit('$prod', '$catn', 'auto', this);">$comment</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo <<<HTML
|
||||
<div class="row py-2 cart_list_target">
|
||||
<div class="row py-2">
|
||||
<div class="mx-auto py-2">
|
||||
Корзина пуста
|
||||
</div>
|
||||
@@ -61,7 +148,8 @@
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div class="row mb-2 mx-0">
|
||||
|
||||
<div class="row mb-3 mx-0">
|
||||
<select id="cart_list_action" class="form-control mr-3 button_clean w-auto" name="CartListAction">
|
||||
<option value="none" hidden>Действие с выбранными</option>
|
||||
<option value="delete" onclick="return cart_list_delete();">Удалить</option>
|
||||
@@ -69,15 +157,78 @@
|
||||
<a class="mr-3 btn button_red button_clean" title="Очистить корзину" href="/cart" role="button" onclick="return cart_delete();">
|
||||
Очистить
|
||||
</a>
|
||||
<p class="ml-auto mr-3 cart_field_cost">
|
||||
<span id="cart_cost">0</span>
|
||||
руб
|
||||
</p>
|
||||
<a class="btn button_clean button_blue" title="Оформить заказ" href="/pay" role="button" onclick="return cart_pay();">
|
||||
Оформить заказ
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-divider mb-3"></div>
|
||||
|
||||
<div class="p-3 mx-0 row">
|
||||
<div id="cart_registration_menu" class="mr-5 col px-0">
|
||||
<div class="row mb-4 mx-0">
|
||||
<label id="cart_registration_individual_button" class="ml-auto btn button_white mb-0 mr-4" for="cart_registration_individual" onclick="cart_registration_choose('cart_registration_individual', <?= $account['_key'] ?>)">Физическое лицо</label>
|
||||
<label id="cart_registration_entity_button" class="mr-auto btn button_white active mb-0" for="cart_registration_entity" onclick="cart_registration_choose('cart_registration_entity', <?= $account['_key'] ?>); cart_registration_entity_init(<?= $account['_key'] ?>)">Юридическое лицо</label>
|
||||
</div>
|
||||
<div class="cart_registration_content d-flex">
|
||||
<input type="radio" id="cart_registration_individual" name="registration_panel" />
|
||||
<div id="cart_registration_individual_body" class="col"></div>
|
||||
<input type="radio" id="cart_registration_entity" name="registration_panel" checked />
|
||||
<div id="cart_registration_entity_body" class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-4 px-0 d-flex flex-column">
|
||||
<div class="mb-3 mx-0 row mt-auto">
|
||||
<?php $form = ActiveForm::begin([
|
||||
'id' => 'form_profile_settings',
|
||||
'action' => false,
|
||||
'fieldConfig' => [
|
||||
'template' => '{label}{input}',
|
||||
],
|
||||
'options' => [
|
||||
'onsubmit' => 'return false;',
|
||||
'class' => 'ml-auto px-0 col'
|
||||
]
|
||||
]);
|
||||
|
||||
// Инициализация
|
||||
$model_delivery ?? $model_delivery = yii::$app->user->identity;
|
||||
$delivery_to_terminal_list ?? $delivery_to_terminal_list = ['Нет данных'];
|
||||
?>
|
||||
|
||||
<small class="mb-2"><b>Терминал для получения</b></small>
|
||||
|
||||
<?= $form->field($model_delivery, 'opts[delivery_to_terminal]', ['options' => ['class' => "mb-0"]])
|
||||
->dropDownList($delivery_to_terminal_list, [
|
||||
'onChange' => 'page_profile_settings(this.parentElement.parentElement, undefined, \'\'); cart_cost_calculate();',
|
||||
'disabled' => count($delivery_to_terminal_list) <= 1
|
||||
])->label(false); ?>
|
||||
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
|
||||
<div class="mb-0 mx-0 row">
|
||||
<div class="ml-auto px-0 col d-flex">
|
||||
<b class="ml-auto my-auto mr-3">
|
||||
<span id="cart_cost">0</span>
|
||||
руб
|
||||
</b>
|
||||
<a class="col-5 btn button_clean button_blue" title="Оформить заказ" href="/orders" role="button" onclick="return cart_request();">Купить</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<script src="/js/cart.js" defer></script>
|
||||
<script src="/js/textarea.js" defer></script>
|
||||
<script src="/js/cart.js" defer></script>
|
||||
<script src="/js/profile.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener('cart.loaded', function(e) {
|
||||
cart_cost_calculate();
|
||||
|
||||
cart_registration_entity_init(<?= $account['_key'] ?>);
|
||||
|
||||
cart_registration_choose('cart_registration_entity', <?= $account['_key'] ?>);
|
||||
});
|
||||
</script>
|
||||
|
@@ -2,15 +2,15 @@
|
||||
|
||||
use yii\helpers\Html;
|
||||
|
||||
$this->title = $name;
|
||||
$this->title = $title;
|
||||
|
||||
?>
|
||||
|
||||
<div id="page_error" class="container py-3">
|
||||
<h1><?= Html::encode($this->title) ?></h1>
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<?= nl2br(Html::encode($message)) ?>
|
||||
<div id="page_error" class="container d-flex flex-column">
|
||||
<div class="my-auto">
|
||||
<div class="col-md-10 col-lg-8 mx-auto p-5 alert alert_white d-flex flex-column">
|
||||
<h2 class="mb-4 text-center gilroy"><b><?= Html::encode($title) ?></b></h4>
|
||||
<p class="text-center"><?= nl2br(Html::encode($description)) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@@ -3,13 +3,12 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
$this->title = 'SkillParts';
|
||||
|
||||
?>
|
||||
|
||||
<link href="/css/ticker.css" rel="stylesheet">
|
||||
<link href="/css/hotline.css" rel="stylesheet">
|
||||
|
||||
<div id="page_index" class="mb-auto">
|
||||
<section class="info_panel mb-4">
|
||||
<section class="info_panel unselectable">
|
||||
<div class="container h-100 d-flex flex-column justify-content-center">
|
||||
<h1 class="mb-4 ml-0 gilroy">Проблема с подбором запчастей?</h1>
|
||||
<p class="ml-0 d-flex">
|
||||
@@ -22,39 +21,36 @@ $this->title = 'SkillParts';
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="h-100 d-flex ticker">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/cummins.png" alt="Cummins">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/iveco.png" alt="Iveco">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/komatsu.png" alt="Komatsu">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/case.png" alt="Case">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/isuzu.png" alt="Isuzu">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/new_holland.png" alt="New Holland">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/perkins.png" alt="Perkins">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/john_deere.png" alt="John Deere">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/caterpillar.png" alt="Caterpillar">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shantui.png" alt="Shantui">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/xcmg.png" alt="XCMG">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/kobelco.png" alt="Kobelco">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shehwa.png" alt="SHEHWA">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/bomag.png" alt="BOMAG">
|
||||
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/hitachi.png" alt="Hitachi">
|
||||
<section id="hotline" class="py-4 hotline unselectable" data-hotline="true" data-hotline-step="1">
|
||||
<article><img src="/img/logos/h32px/compressed/cummins.png" alt="Cummins"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/iveco.png" alt="Iveco"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/komatsu.png" alt="Komatsu"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/case.png" alt="Case"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/isuzu.png" alt="Isuzu"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/new_holland.png" alt="New Holland"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/perkins.png" alt="Perkins"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/john_deere.png" alt="John Deere"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/caterpillar.png" alt="Caterpillar"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/shantui.png" alt="Shantui"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/xcmg.png" alt="XCMG"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/kobelco.png" alt="Kobelco"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/shehwa.png" alt="SHEHWA"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/bomag.png" alt="BOMAG"></article>
|
||||
<article><img src="/img/logos/h32px/compressed/hitachi.png" alt="Hitachi"></article>
|
||||
</section>
|
||||
|
||||
<section class="container mb-4">
|
||||
<!-- <div class="row mb-3">
|
||||
<h4 class="col gilroy categories_blocks_panel_title">Сопутствующие товары</h4>
|
||||
</div> -->
|
||||
<div class="row mb-5 mb-md-0 px-3 px-md-0">
|
||||
<div class="row mb-5 mb-md-0 px-3 px-md-0 unselectable">
|
||||
<div class="col-12 col-md-6 col-lg-4 mb-4 mb-lg-0 py-0 d-flex flex-column">
|
||||
<div class="px-3 px-xl-4 pt-3 d-inline-block category_block_title">
|
||||
<h4 class="m-0">Масла, смазки</h4>
|
||||
</div>
|
||||
<div class="p-3 px-md-4 category_block">
|
||||
<dl class="mb-0">
|
||||
<dd>Масла моторные</dd>
|
||||
<dd>Масла трансмиссионные</dd>
|
||||
<dd>Масла гидравлические</dd>
|
||||
<dd>Смазки</dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Масла моторные</a></dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Масла трансмиссионные</a></dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Масла гидравлические</a></dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Смазки</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -64,7 +60,7 @@ $this->title = 'SkillParts';
|
||||
</div>
|
||||
<div class="p-3 px-md-4 category_block">
|
||||
<dl class="mb-0">
|
||||
<dd>Фары и свет</dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Фары и свет</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,9 +70,9 @@ $this->title = 'SkillParts';
|
||||
</div>
|
||||
<div class="p-3 px-md-4 category_block">
|
||||
<dl class="mb-0">
|
||||
<dd>Шприцы для смазки </dd>
|
||||
<dd>Ключи, съёмники</dd>
|
||||
<dd>Наборы инструментов</dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Шприцы для смазки</a></dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Ключи, съёмники</a></dd>
|
||||
<dd><a onclick="return writeInDevelopment(this);" type="button">Наборы инструментов</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,4 +80,13 @@ $this->title = 'SkillParts';
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="/js/ticker.js" defer></script>
|
||||
<script src="/js/hotline.js" defer></script>
|
||||
<script src="/js/text.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener('hotline.loaded', function(e) {
|
||||
// Загружена программа: "бегущая строка"
|
||||
|
||||
// Обработка HTML-документа и генерация бегущих строк
|
||||
e.detail.hotline.preprocessing();
|
||||
});
|
||||
</script>
|
||||
|
BIN
mirzaev/skillparts/system/views/invoice/order/pattern.pdf
Normal file
BIN
mirzaev/skillparts/system/views/invoice/order/pattern.pdf
Normal file
Binary file not shown.
210
mirzaev/skillparts/system/views/invoice/order/pattern.php
Normal file
210
mirzaev/skillparts/system/views/invoice/order/pattern.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
use app\models\Settings;
|
||||
use app\models\Product;
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Счёт №<?= $data['order']->_key ?></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<table style="height: 100vh;" cols="13" rows="50">
|
||||
<tr>
|
||||
<td style="text-align: center; outline: 2px solid;" colspan="13" rowspan="3" valign="center"><b>ВНИМАНИЕ! Уважаемые покупатели, просим вас указывать в назначении платежа: Аванс за запчасти по договору №<?= $buyer['id'] ?></b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="text-align: center;" colspan="13"><b>Образец заполнения платежного поручения</b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="border: solid;" colspan="6" rowspan="2" valign="top">АО "ТИНЬКОФФ БАНК" г. Москва</td>
|
||||
<td style="border: solid;" valign="top">БИК</td>
|
||||
<td style="border: solid;" colspan="6" valign="top">044525974</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="border: solid;" rowspan="2" valign="top">Сч. №</td>
|
||||
<td style="border: solid;" colspan="6" rowspan="2" valign="top">30101810145250000974</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="border: solid;" colspan="6" valign="top">Банк получателя</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="border: solid;" valign="top">ИНН</td>
|
||||
<td style="text-align: left; border: solid;" colspan="2" valign="top">2724241607</td>
|
||||
<td style="border: solid;" valign="top">КПП</td>
|
||||
<td style="text-align: left; border: solid;" colspan="2" valign="top">272401001</td>
|
||||
<td style="border: solid;" valign="top">Сч. №</td>
|
||||
<td style="text-align: left; border: solid;" colspan="6" valign="top">40702810610000696279</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="border: solid;" colspan="6" rowspan="3" valign="top">ООО "СтандартМашинери"</td>
|
||||
<td style="border: solid;" valign="top">Вид оп.</td>
|
||||
<td style="text-align: left; border: solid;" valign="top">01</td>
|
||||
<td style="border: solid;" colspan="2" valign="top">Срок плат.</td>
|
||||
<td style="text-align: left; border: solid;" colspan="3" valign="top"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="border: solid;" valign="top">Наз. пл.</td>
|
||||
<td style="border: solid;"></td>
|
||||
<td style="border: solid;" colspan="2" valign="top">Очер. плат.</td>
|
||||
<td style="text-align: left; border: solid;" colspan="3" valign="top">5</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="border: solid;" valign="top">Код</td>
|
||||
<td style="border: solid;"></td>
|
||||
<td style="border: solid;" colspan="2" valign="top">Рез. поле</td>
|
||||
<td style="text-align: left; border: solid;" colspan="3" valign="top"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="text-indent: 2px; text-align: left; border-bottom: medium; font-size: 18rem;" colspan="13" rowspan="3" valign="center">
|
||||
<?php
|
||||
// Инициализация часового пояса
|
||||
|
||||
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
|
||||
$timezone = $timezone[1][0];
|
||||
?>
|
||||
<b>Счет на оплату №<?= $data['order']->_key ?> от <?= (new DateTime())->setTimestamp($date)->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?></b>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" rowspan="2" valign="center">Поставщик</td>
|
||||
<td style="word-wrap: break-word;" colspan="11" rowspan="2" valign="top"><b>ООО "СтандартМашинери", ИНН 2724241607, КПП 272401001, 680014, Хабаровский край, Хабаровск г, Промышленная ул, дом 3, офис 105, тел.: 89242128879</b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" rowspan="2" valign="center">Покупатель</td>
|
||||
<td style="word-wrap: break-word;" colspan="11" rowspan="2" valign="top"><b><?= $buyer['info'] ?></b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="text-align: center; border: solid; border-top: thick; border-left: thick;" colspan="1" rowspan="2" valign="center"><b>№</b></td>
|
||||
<td style="text-align: center; border: solid; border-top: thick;" colspan="6" rowspan="2" valign="center"><b>Товар</b></td>
|
||||
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Количество</b></td>
|
||||
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Цена</b></td>
|
||||
<td style="text-align: center; border: solid; border-top: thick; border-right: thick;" colspan="2" rowspan="2" valign="center"><b>Сумма</b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<?php
|
||||
// Инициализация счётчика строк
|
||||
$row = 1;
|
||||
|
||||
// Инициализация итоговой цены
|
||||
$cost = 0;
|
||||
?>
|
||||
|
||||
<?php foreach ($data['supplies'] as $prod => $supplies) : ?>
|
||||
<?php foreach ($supplies as $catn => $deliveries) : ?>
|
||||
<?php foreach ($deliveries as $delivery => $supply) : ?>
|
||||
<?php
|
||||
// Инициализация названия
|
||||
$name = Product::searchByCatn($catn)->name ?? 'Без названия';
|
||||
?>
|
||||
<tr>
|
||||
<td style="text-align: center; border: solid; border-left: thick;" colspan="1" valign="center"><?= $row++ ?></td>
|
||||
<td style="text-align: left; border: solid;" colspan="6" valign="center"><?= $prod ?> <?= $catn ?> <?= $name ?></td>
|
||||
<td style="text-align: center; border: solid;" colspan="2" valign="center"><?= $supply['amount'] ?></td>
|
||||
<td style="text-align: center; border: solid;" valign="center"><?= $supply['cost'] * $supply['amount'] ?></td>
|
||||
<td style="text-align: center; border: solid;" valign="center"><?= $supply['currency'] ?></td>
|
||||
<td style="text-align: center; border: solid; border-right: thick;" colspan="2" valign="center"><?= $cost += $supply['cost'] * $supply['amount'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php endforeach ?>
|
||||
<?php endforeach ?>
|
||||
|
||||
<tr>
|
||||
<td style="border-top: thick;" colspan="13"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="9"></td>
|
||||
<td style="text-align: right;" colspan="2" valign="center"><b>Итого:</b></td>
|
||||
<td colspan="2" valign="center"><b><?= $cost - $tax = $cost * 0.2 ?></b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="9"></td>
|
||||
<td style="text-align: right;" colspan="2" valign="center"><b>В т.ч. НДС (20%):</b></td>
|
||||
<td colspan="2" valign="center"><b><?= $tax ?></b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="9"></td>
|
||||
<td style="text-align: right;" colspan="2" valign="center"><b>Итого с НДС:</b></td>
|
||||
<td colspan="2" valign="center"><b><?= $cost ?></b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13" valign="center">Всего наименований <?= $row - 1 ?>, на сумму <?= $cost ?> руб.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="13" valign="center"><b><?= mb_strtoupper(mb_substr($text = mb_strtolower($text = (new NumberFormatter("ru", NumberFormatter::SPELLOUT))->format($cost), 'UTF-8'), 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($text, 1, null, 'UTF-8') ?> рублей</b></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="width: 8pt;" colspan="13"><img src="<?= YII_PATH_PUBLIC . '/img/invoices/signature.png' ?>" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
@@ -13,30 +13,15 @@ AppAsset::register($this);
|
||||
<?php $this->beginPage() ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= Yii::$app->language ?>">
|
||||
<html lang="<?= yii::$app->language ?>">
|
||||
|
||||
<head>
|
||||
<meta charset="<?= Yii::$app->charset ?>">
|
||||
<meta charset="<?= yii::$app->charset ?>">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/favicons/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/favicons/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/favicons/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/favicons/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/favicons/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/favicons/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/favicons/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/favicons/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/favicons/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicons/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png">
|
||||
<link rel="manifest" href="/favicons/manifest.json">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="/favicons/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
||||
|
||||
<?php $this->registerCsrfMetaTags() ?>
|
||||
<title><?= Html::encode($this->title ?? 'SkillParts') ?></title>
|
||||
@@ -46,9 +31,9 @@ AppAsset::register($this);
|
||||
<body>
|
||||
<?php $this->beginBody() ?>
|
||||
|
||||
<div id="notifications_popup_wrap" class="col-3 m-4"></div>
|
||||
<div id="notifications_popup_wrap" class="m-4 col-6 col-sm-5 col-md-4 col-lg-4 col-xl-3"></div>
|
||||
|
||||
<header class="container pt-2 mt-1 mb-2 mb-sm-4">
|
||||
<header class="container pt-2 mt-1 mb-2 mb-sm-4 unselectable">
|
||||
<div class="row h-100">
|
||||
<div id="logo" class="col-3 col-md-4 h-100">
|
||||
<a class="py-2 h-100 d-inline-flex" title="SkillParts" href="/" role="button" onclick="return page_main();">
|
||||
@@ -65,47 +50,34 @@ AppAsset::register($this);
|
||||
<menu class="col-auto col-lg-4 mb-0 d-flex justify-content-end"></menu>
|
||||
</div>
|
||||
<div class="h-divider"></div>
|
||||
<script src="/js/js.cookie.min.js" defer></script>
|
||||
</header>
|
||||
|
||||
<aside class="container mb-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 d-flex flex-column align-center justify-content-end dropdownMenuButton_column">
|
||||
<button id="catalog" class="btn form-control d-flex align-items-center button_clean catalog_button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button id="catalog" class="btn button_clean form-control d-flex align-items-center button_clean catalog_button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-bars col-auto text-left p-0 mr-auto h-100 d-flex flex-column justify-content-center"></i>
|
||||
<p class="col-10 m-0 p-0">Каталог товаров</p>
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="catalog">
|
||||
<a class="dropdown-item" href="#">Action</a>
|
||||
<a class="dropdown-item" href="#">Another action</a>
|
||||
<a class="dropdown-item" href="#">Something else here</a>
|
||||
<a class="dropdown-item" type="button">В разработке</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="searchPanel" class="col">
|
||||
<!-- <input id="catalog_search_panel_button_1" class="btn btn-sm button_clean" type="radio" name="catalog_search_panel_buttons" value="catalog_search_panel_button_1" checked>
|
||||
<label class="mb-0 px-3 px-md-4 py-1" for="catalog_search_panel_button_1">Номер детали</label>
|
||||
<input id="catalog_search_panel_button_2" class="btn btn-sm text-white button_clean" type="radio" name="catalog_search_panel_buttons" value="catalog_search_panel_button_2">
|
||||
<label class="mb-0 px-3 px-md-4 py-1" for="catalog_search_panel_button_2">Вторая кнопка</label>
|
||||
<input id="catalog_search_panel_button_3" class="btn btn-sm5 text-white button_clean" type="radio" name="catalog_search_panel_buttons" value="catalog_search_panel_button_3">
|
||||
<label class="mb-0 px-3 px-md-4 py-1" for="catalog_search_panel_button_3">Третья кнопка</label> -->
|
||||
<form class="d-flex catalog_search" onsubmit="return false;">
|
||||
<div class="position-relative col-sm-8 col-lg-10 px-0">
|
||||
<input id="search_line" type="text" class="form-control col-12 catalog_search_line button_clean" placeholder="Введите номер запчасти, например: 45223503481" oninput="$('#search_line').dropdown('hide'); product_search(this.value);" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" autocomplete="off">
|
||||
<input id="search_line" type="text" class="form-control button_clean col-12 catalog_search_line" placeholder="Введите номер запчасти, например: 45223503481" oninput="$('#search_line').dropdown('hide'); return product_search(this.value);" data-toggle="dropdown" aria-expanded="false" autocomplete="off">
|
||||
<?php
|
||||
// Сделать системные настройки и по ним работать
|
||||
$search_panel = yii::$app->controller->renderPartial('/search/panel', ['history' => true]);
|
||||
|
||||
// if (!yii::$app->user->isGuest && $search_panel = $search_panel ?? yii::$app->controller->renderPartial('/search/panel', ['history' => true])) {
|
||||
echo <<<HTML
|
||||
<div id="search_line_window" class="dropdown-menu w-100" aria-labelledby="search_line">
|
||||
$search_panel
|
||||
</div>
|
||||
HTML;
|
||||
// } else {
|
||||
// echo <<<HTML
|
||||
// <div id="search_line_window" class="dropdown-menu w-100" style="display: none" aria-labelledby="search_line"></div>
|
||||
// HTML;
|
||||
// }
|
||||
<div id="search_line_window" class="dropdown-menu w-100" aria-labelledby="search_line">
|
||||
$search_panel
|
||||
</div>
|
||||
HTML;
|
||||
?>
|
||||
</div>
|
||||
<button type="submit" class="col btn button_clean catalog_search_button" onclick="product_search(this.parentElement.getElementsByTagName('input')[0].value, 1)">ПОИСК</button>
|
||||
@@ -121,24 +93,28 @@ AppAsset::register($this);
|
||||
<footer class="container py-4">
|
||||
<div class="row px-3">
|
||||
<div class="col-12 col-md-auto mr-md-5">
|
||||
<h5 class="row mb-2"><b>Контакты</b></h5>
|
||||
<h5 class="row mb-2 unselectable"><b>Контакты</b></h5>
|
||||
<small class="row mb-1"><b>Адрес: </b>Хабаровск, Промышленная 3, 105</small>
|
||||
<small class="row mb-1"><b>Время работы: </b>пн-пт 09:00-18:00</small>
|
||||
<small class="row mb-1"><b>Телефон: </b>+7 (4212) 35-85-34</small>
|
||||
<small class="row mb-1"><b>Почта: </b>info@skillparts.ru</small>
|
||||
<small class="row mb-1"><b>Телефон: </b><a href="tel:+74212358534">+7 (4212) 35-85-34</a></small>
|
||||
<small class="row"><b>Почта: </b><a href="mailto:info@skillparts.ru">info@skillparts.ru</a></small>
|
||||
</div>
|
||||
<div class="col-md-auto mr-md-5 partnership">
|
||||
<div class="col-md-auto mr-md-5 partnership unselectable">
|
||||
<h5 class="row mb-2"><b>Партнёрство</b></h5>
|
||||
<small class="row mb-1"><a>Оптовым покупателям</a></small>
|
||||
<small class="row mb-1"><a>Поставщикам</a></small>
|
||||
<small class="row mb-1"><a>Партнерская сеть</a></small>
|
||||
<small class="row mb-1"><a href="/buyers">Покупателям</a></small>
|
||||
<small class="row mb-1"><a href="/suppliers">Поставщикам</a></small>
|
||||
<small class="row"><a href="/partners">Сеть филиалов</a></small>
|
||||
</div>
|
||||
<div class="mt-auto ml-auto col-auto unselectable">
|
||||
<small class="row"><a href="mailto:info@skillparts.ru?subject=Ошибка на сайте">Сообщить об ошибке</a></small>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/js/loading.js" defer></script>
|
||||
<?php $this->endBody() ?>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<?php $this->endPage() ?>
|
||||
<?php $this->endPage() ?>
|
||||
|
14
mirzaev/skillparts/system/views/mails/password.php
Normal file
14
mirzaev/skillparts/system/views/mails/password.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<div style="padding: 0 14%;">
|
||||
<div style="background: #fff;">
|
||||
<a title="SkillParts" href="https://skillparts.ru">
|
||||
<img style="width: 150px;" src="https://skillparts.ru/img/logos/skillparts.png" alt="SkillParts">
|
||||
</a>
|
||||
</div>
|
||||
<div style="background: #f0eefb; padding: 40px; margin: 30px 0;">
|
||||
<h3 style="text-align: center; margin-bottom: 30px;"><b>Новый пароль</b></h3>
|
||||
<p style="margin: 0 40px; margin-bottom: 8px;">По вашему запросу сгенерирован новый пароль: <b><?= $pswd ?? 'ОШИБКА' ?></b></p>
|
||||
</div>
|
||||
<div style="background: #fff;">
|
||||
<small>Если это были не вы свяжитесь с администрацией</small>
|
||||
</div>
|
||||
</div>
|
15
mirzaev/skillparts/system/views/mails/restore.php
Normal file
15
mirzaev/skillparts/system/views/mails/restore.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<div style="padding: 0 14%;">
|
||||
<div style="background: #fff;">
|
||||
<a title="SkillParts" href="https://skillparts.ru">
|
||||
<img style="width: 150px;" src="https://skillparts.ru/img/logos/skillparts.png" alt="SkillParts">
|
||||
</a>
|
||||
</div>
|
||||
<div style="background: #f0eefb; padding: 40px; margin: 30px 0;">
|
||||
<h3 style="text-align: center; margin-bottom: 30px;"><b>Генерация нового пароля</b></h3>
|
||||
<p style="margin: 0 40px; margin-bottom: 8px;">Только что был получен запрос на генерацию нового пароля для вашего аккаунта</p>
|
||||
<a style="display: block; text-align: center;" href="https://skillparts.ru/restore/<?= $id ?? '' ?>/<?= $chpk ?? '' ?>">Подтвердить</a>
|
||||
</div>
|
||||
<div style="background: #fff;">
|
||||
<small>Если это были не вы, проигнорируйте это письмо</small>
|
||||
</div>
|
||||
</div>
|
9
mirzaev/skillparts/system/views/mails/supplier.php
Normal file
9
mirzaev/skillparts/system/views/mails/supplier.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<html>
|
||||
|
||||
<body>
|
||||
<p>ФИО: <span style="margin-left: auto;"><?= $name ?></span></p>
|
||||
<p>Телефон: <span style="margin-left: auto;"><?= $phon ?></span></p>
|
||||
<p>Почта: <span style="margin-left: auto;"><?= $mail ?></span></p>
|
||||
</body>
|
||||
|
||||
</html>
|
15
mirzaev/skillparts/system/views/mails/suppliers/decline.php
Normal file
15
mirzaev/skillparts/system/views/mails/suppliers/decline.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<div style="padding: 0 14%;">
|
||||
<div style="background: #fff;">
|
||||
<a title="SkillParts" href="https://skillparts.ru">
|
||||
<img style="width: 150px;" src="https://skillparts.ru/img/logos/skillparts.png" alt="SkillParts">
|
||||
</a>
|
||||
</div>
|
||||
<div style="background: #f0eefb; padding: 40px; margin: 30px 0;">
|
||||
<h3 style="text-align: center; margin-bottom: 30px;"><b>Отказано в регистрации</b></h3>
|
||||
<p style="margin: 0 40px; margin-bottom: 8px;"><b>Причина: </b><?= $reason ?? 'Ошибка' ?></p>
|
||||
<a style="display: block; text-align: center;" href="https://skillparts.loc/suppliers/request">Повторная заявка</a>
|
||||
</div>
|
||||
<div style="background: #fff;">
|
||||
<small>Если это были не вы свяжитесь с администрацией</small>
|
||||
</div>
|
||||
</div>
|
18
mirzaev/skillparts/system/views/mails/verify.php
Normal file
18
mirzaev/skillparts/system/views/mails/verify.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<div style="padding: 0 14%;">
|
||||
<div style="background: #fff;">
|
||||
<a title="SkillParts" href="https://skillparts.ru">
|
||||
<img style="width: 150px;" src="https://skillparts.ru/img/logos/skillparts.png" alt="SkillParts">
|
||||
</a>
|
||||
</div>
|
||||
<div style="background: #f0eefb; padding: 40px; margin: 30px 0;">
|
||||
<h3 style="text-align: center; margin-bottom: 30px;"><b>Подтвердите регистрацию</b></h3>
|
||||
<p style="margin: 0 40px; margin-bottom: 8px;"><b>Ваш пароль: </b><?= $password ?? 'Ошибка' ?></p>
|
||||
<small style="display: block; margin: 0 40px; margin-bottom: 40px;">Нажимая на кнопку ниже вы соглашаетесь с <a href="https://skillparts.ru/policy">политикой конфиденциальности</a></small>
|
||||
<a style="display: block; text-align: center;" href="https://skillparts.ru/verify/<?= $vrfy ?? '' ?>">Принять и подтвердить</a>
|
||||
</div>
|
||||
<div style="background: #fff;">
|
||||
<small>Вы получили это сообщение потому, что на ваш почтовый адрес была совершена регистрация</small>
|
||||
</br>
|
||||
<small>Если это были не вы свяжитесь с администрацией</small>
|
||||
</div>
|
||||
</div>
|
@@ -1,18 +1,18 @@
|
||||
<a id="notification_button" class="text-dark d-flex h-100 mr-2" title="Уведомления" role="button" data-toggle="dropdown" data-offset="-100%p + 100%" onclick="return notification_stream();">
|
||||
<a id="notification_button" class="mr-2 h-100 text-dark d-flex" title="Уведомления" role="button" data-toggle="dropdown" data-offset="-100%p + 100%" onclick="return notification_stream();">
|
||||
<?php
|
||||
if (empty($notifications_new_amount) || $notifications_new_amount < 1) {
|
||||
// Новые уведомления не найдены
|
||||
|
||||
echo <<<HTML
|
||||
<i class="fas fa-bell my-auto mx-2"></i>
|
||||
<i class="mx-2 fas fa-bell my-auto"></i>
|
||||
HTML;
|
||||
} else {
|
||||
// Новые уведомления найдены
|
||||
|
||||
echo <<<HTML
|
||||
<small class="my-auto ml-2 mr-1"><b>$notifications_new_amount</b></small>
|
||||
<i class="fas fa-bell my-auto mr-2 notification_button_active"></i>
|
||||
<small class="ml-2 mr-1 my-auto"><b>$notifications_new_amount</b></small>
|
||||
<i class="mr-2 my-auto fas fa-bell notification_button_active"></i>
|
||||
HTML;
|
||||
}
|
||||
?>
|
||||
</a>
|
||||
</a>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user