Compare commits
19 Commits
12
README.md
12
README.md
|
@ -1,10 +1,6 @@
|
|||
# Ebala
|
||||
### Site for providing outsourced employees to retail stores
|
||||
Site-registry of tasks for outsourced employees
|
||||
|
||||
My customer uses this development to process thousands of applications throughout Krasnoyarsk and neighboring cities.<br/>
|
||||
**9 000 000** Russian rubles pass through the site every **week** 🤟.
|
||||
|
||||
For this project I received **600 000** Russian rubles. At the current exchange rate this is **$6000**.<br/>
|
||||
If I had not been lazy and submitted the order a few months ago, it would have been **$8000** 😥 (super sad).
|
||||
|
||||
I did not sign any documents prohibiting the dissemination of information. You can use the code under the **WTFPL** license 🤝 (read ./LICENSE)
|
||||
From this project i earned >**1 000 000** Russian rubles</br>
|
||||
**DEVELOPMENT COMPLETED. PROJECT CLOSED.**</br>
|
||||
</br>
|
||||
|
|
|
@ -30,16 +30,17 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "~8.2",
|
||||
"ext-sodium": "~8.2.4",
|
||||
"php": "~8.3",
|
||||
"ext-sodium": "~8.3",
|
||||
"mirzaev/minimal": "^2.0.x-dev",
|
||||
"mirzaev/accounts": "~1.2.x-dev",
|
||||
"mirzaev/arangodb": "^1.0.0",
|
||||
"triagens/arangodb": "~3.9.x-dev",
|
||||
"twig/twig": "^3.4",
|
||||
"twig/twig": "^3.10",
|
||||
"twig/extra-bundle": "^3.7",
|
||||
"twig/intl-extra": "^3.7",
|
||||
"phpoffice/phpspreadsheet": "^1.29"
|
||||
"twig/intl-extra": "^3.10",
|
||||
"phpoffice/phpspreadsheet": "^2.1",
|
||||
"openswoole/core": "^22.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~9.5"
|
||||
|
|
|
@ -4,69 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "588f1020cbf90d4c8ed02b057592c14f",
|
||||
"content-hash": "c0be2095032e176b9fb16a24e7a4d1a1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "ezyang/htmlpurifier",
|
||||
"version": "v4.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ezyang/htmlpurifier.git",
|
||||
"reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8",
|
||||
"reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"cerdic/css-tidy": "^1.7 || ^2.0",
|
||||
"simpletest/simpletest": "dev-master"
|
||||
},
|
||||
"suggest": {
|
||||
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
|
||||
"ext-bcmath": "Used for unit conversion and imagecrash protection",
|
||||
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
|
||||
"ext-tidy": "Used for pretty-printing HTML"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"library/HTMLPurifier.composer.php"
|
||||
],
|
||||
"psr-0": {
|
||||
"HTMLPurifier": "library/"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/library/HTMLPurifier/Language/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Edward Z. Yang",
|
||||
"email": "admin@htmlpurifier.org",
|
||||
"homepage": "http://ezyang.com"
|
||||
}
|
||||
],
|
||||
"description": "Standards compliant HTML filter written in PHP",
|
||||
"homepage": "http://htmlpurifier.org/",
|
||||
"keywords": [
|
||||
"html"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ezyang/htmlpurifier/issues",
|
||||
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0"
|
||||
},
|
||||
"time": "2022-09-18T07:06:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.7.0",
|
||||
|
@ -728,17 +667,88 @@
|
|||
"time": "2023-03-20T11:46:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"version": "1.29.0",
|
||||
"name": "openswoole/core",
|
||||
"version": "22.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0"
|
||||
"url": "https://github.com/openswoole/core.git",
|
||||
"reference": "06dae68fdac73341ccf565ecef388434bd893141"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0",
|
||||
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0",
|
||||
"url": "https://api.github.com/repos/openswoole/core/zipball/06dae68fdac73341ccf565ecef388434bd893141",
|
||||
"reference": "06dae68fdac73341ccf565ecef388434bd893141",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-openswoole": ">=22.0",
|
||||
"php": ">=7.4",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/http-server-middleware": "^1.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"ext-sockets": "*",
|
||||
"friendsofphp/php-cs-fixer": "^3.6",
|
||||
"openswoole/ide-helper": "^22.0",
|
||||
"php-http/psr7-integration-tests": "^1.1",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mysqli": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-redis": "Required to use redis database, and the required version is greater than or equal to 3.1.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Coroutine/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"OpenSwoole\\Core\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "OpenSwoole Group",
|
||||
"email": "hello@openswoole.com"
|
||||
}
|
||||
],
|
||||
"description": "Openswoole core library",
|
||||
"homepage": "https://openswoole.com",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http2",
|
||||
"mqtt",
|
||||
"openswoole",
|
||||
"php",
|
||||
"tcp",
|
||||
"websocket"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://openswoole.com/docs",
|
||||
"issues": "https://github.com/openswoole/openswoole/issues",
|
||||
"pull-request": "https://github.com/openswoole/openswoole/pulls",
|
||||
"source": "https://github.com/openswoole/openswoole"
|
||||
},
|
||||
"time": "2023-12-10T19:02:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||
"reference": "dbed77bd3a0f68f96c0dd68ad4499d5674fecc3e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/dbed77bd3a0f68f96c0dd68ad4499d5674fecc3e",
|
||||
"reference": "dbed77bd3a0f68f96c0dd68ad4499d5674fecc3e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -755,25 +765,24 @@
|
|||
"ext-xmlwriter": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": "*",
|
||||
"ezyang/htmlpurifier": "^4.15",
|
||||
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||
"markbaker/complex": "^3.0",
|
||||
"markbaker/matrix": "^3.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"php": "^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"dompdf/dompdf": "^1.0 || ^2.0",
|
||||
"dompdf/dompdf": "^2.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.3",
|
||||
"mpdf/mpdf": "^8.1.1",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpstan/phpstan": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0 || ^10.0",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"tecnickcom/tcpdf": "^6.5"
|
||||
},
|
||||
|
@ -828,9 +837,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0"
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/2.1.0"
|
||||
},
|
||||
"time": "2023-06-14T22:48:31+00:00"
|
||||
"time": "2024-05-11T04:17:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
|
@ -1144,6 +1153,119 @@
|
|||
},
|
||||
"time": "2023-04-04T09:54:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-handler",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-handler.git",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side request handler",
|
||||
"keywords": [
|
||||
"handler",
|
||||
"http",
|
||||
"http-interop",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"server"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-10T20:06:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-middleware",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-middleware.git",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/http-server-handler": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side middleware",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-interop",
|
||||
"middleware",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/http-server-middleware/issues",
|
||||
"source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-11T06:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "3.0.0",
|
||||
|
@ -1619,16 +1741,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.3.0",
|
||||
"version": "v3.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
|
||||
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
|
||||
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
|
||||
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1637,7 +1759,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.4-dev"
|
||||
"dev-main": "3.5-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
|
@ -1666,7 +1788,7 @@
|
|||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1682,7 +1804,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-05-23T14:45:45+00:00"
|
||||
"time": "2024-04-18T09:32:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/error-handler",
|
||||
|
@ -2377,25 +2499,25 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/intl",
|
||||
"version": "v6.3.2",
|
||||
"version": "v7.0.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/intl.git",
|
||||
"reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69"
|
||||
"reference": "dd12042707110995e2e7d80103f8d9928bea8621"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/intl/zipball/1f8cb145c869ed089a8531c51a6a4b31ed0b3c69",
|
||||
"reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69",
|
||||
"url": "https://api.github.com/repos/symfony/intl/zipball/dd12042707110995e2e7d80103f8d9928bea8621",
|
||||
"reference": "dd12042707110995e2e7d80103f8d9928bea8621",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/filesystem": "^5.4|^6.0",
|
||||
"symfony/finder": "^5.4|^6.0",
|
||||
"symfony/var-exporter": "^5.4|^6.0"
|
||||
"symfony/filesystem": "^6.4|^7.0",
|
||||
"symfony/finder": "^6.4|^7.0",
|
||||
"symfony/var-exporter": "^6.4|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -2403,7 +2525,8 @@
|
|||
"Symfony\\Component\\Intl\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
"/Tests/",
|
||||
"/Resources/data/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
|
@ -2439,7 +2562,7 @@
|
|||
"localization"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/intl/tree/v6.3.2"
|
||||
"source": "https://github.com/symfony/intl/tree/v7.0.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2455,20 +2578,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-20T07:43:09+00:00"
|
||||
"time": "2024-04-18T09:29:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.27.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2482,9 +2605,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -2521,7 +2641,7 @@
|
|||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2537,20 +2657,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.27.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2564,9 +2684,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -2604,7 +2721,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2620,20 +2737,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2641,9 +2758,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -2687,7 +2801,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2703,7 +2817,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php83",
|
||||
|
@ -3516,25 +3630,25 @@
|
|||
},
|
||||
{
|
||||
"name": "twig/intl-extra",
|
||||
"version": "v3.7.1",
|
||||
"version": "v3.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/intl-extra.git",
|
||||
"reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d"
|
||||
"reference": "693f6beb8ca91fc6323e01b3addf983812f65c93"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/4f4fe572f635534649cc069e1dafe4a8ad63774d",
|
||||
"reference": "4f4fe572f635534649cc069e1dafe4a8ad63774d",
|
||||
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/693f6beb8ca91fc6323e01b3addf983812f65c93",
|
||||
"reference": "693f6beb8ca91fc6323e01b3addf983812f65c93",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.3",
|
||||
"symfony/intl": "^5.4|^6.0",
|
||||
"twig/twig": "^2.7|^3.0"
|
||||
"php": ">=7.2.5",
|
||||
"symfony/intl": "^5.4|^6.4|^7.0",
|
||||
"twig/twig": "^3.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^5.4|^6.3"
|
||||
"symfony/phpunit-bridge": "^6.4|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -3564,7 +3678,7 @@
|
|||
"twig"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/twigphp/intl-extra/tree/v3.7.1"
|
||||
"source": "https://github.com/twigphp/intl-extra/tree/v3.10.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3576,33 +3690,41 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-29T15:34:56+00:00"
|
||||
"time": "2024-05-11T07:35:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.6.1",
|
||||
"version": "v3.10.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd"
|
||||
"reference": "67f29781ffafa520b0bbfbd8384674b42db04572"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
|
||||
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572",
|
||||
"reference": "67f29781ffafa520b0bbfbd8384674b42db04572",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-mbstring": "^1.3"
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-php80": "^1.22"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/container": "^1.0|^2.0",
|
||||
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Resources/core.php",
|
||||
"src/Resources/debug.php",
|
||||
"src/Resources/escaper.php",
|
||||
"src/Resources/string_loader.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Twig\\": "src/"
|
||||
}
|
||||
|
@ -3635,7 +3757,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.6.1"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.10.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3647,7 +3769,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-06-08T12:52:13+00:00"
|
||||
"time": "2024-05-16T10:04:27+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
@ -5393,8 +5515,8 @@
|
|||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "~8.2",
|
||||
"ext-sodium": "~8.2.4"
|
||||
"php": "~8.3",
|
||||
"ext-sodium": "~8.3"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.2.0"
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=Ebala-socket
|
||||
|
||||
Wants=network.target
|
||||
After=syslog.target network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=sudo -u www-data /usr/bin/php /var/www/ebala/mirzaev/ebala/system/public/socket.php
|
||||
PIDFile=/var/run/php/ebala-socket.pid
|
||||
RemainAfterExit=no
|
||||
RuntimeMaxSec=3600s
|
||||
Restart=always
|
||||
RestartSec=30s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -8,6 +8,7 @@ namespace mirzaev\ebala\controllers;
|
|||
use mirzaev\ebala\controllers\core,
|
||||
mirzaev\ebala\controllers\traits\errors,
|
||||
mirzaev\ebala\models\account as model,
|
||||
mirzaev\ebala\models\task,
|
||||
mirzaev\ebala\models\registry,
|
||||
mirzaev\ebala\models\core as _core;
|
||||
|
||||
|
@ -27,6 +28,37 @@ final class account extends core
|
|||
{
|
||||
use errors;
|
||||
|
||||
/**
|
||||
* Страница аккаунта
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function index(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status()) {
|
||||
// Авторизован аккаунт
|
||||
|
||||
// Инициализация истории заявок
|
||||
$this->view->history = task::list(before: 'FILTER task.worker == "' . model::worker($this->account->getId())?->id . '"');
|
||||
|
||||
// Инициализация баланса счёта
|
||||
// В будущем сделать перебор по всем связанным с аккаунтам сотрудникам и магазинам (сейчас только 1 сотрудник может быть)
|
||||
$this->view->balance = 0;
|
||||
foreach (task::list(before: 'FILTER task.worker == "' . model::worker($this->account->getId())?->id . '" && task.result.processed == false') as $task) $this->view->balance += $task->task['result']['hours'] * $task->task['result']['hour'] + $task->task['result']['penalty'] + $task->task['result']['bonus'];
|
||||
|
||||
// Генерация представления
|
||||
$main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'account.html');
|
||||
} else $main = $this->authorization();
|
||||
|
||||
// Возврат (успех)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]);
|
||||
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main;
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Прочитать данные
|
||||
*
|
||||
|
@ -38,7 +70,7 @@ final class account extends core
|
|||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных аккаунта
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, commentary: d.commentary }')->getAll();
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, commentary: d.commentary, transactions: d.transactions }')->getAll();
|
||||
|
||||
if (!empty($account)) {
|
||||
// Найдены данные аккаунта
|
||||
|
@ -74,113 +106,158 @@ final class account extends core
|
|||
*/
|
||||
public function update(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных аккаунта
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
// Инициализация данных аккаунта
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
|
||||
if (!empty($account)) {
|
||||
// Найден аккаунт
|
||||
if (!empty($account)) {
|
||||
// Найден аккаунт
|
||||
|
||||
// Инициализация буфера изменённости пароля
|
||||
$password = false;
|
||||
// Инициализация буфера изменённости пароля
|
||||
$password = false;
|
||||
|
||||
// Инициализация параметров (перезапись переданными значениями)
|
||||
if ($parameters['name_first'] !== $account->name['first']) $account->name = ['first' => $parameters['name_first']] + $account->name;
|
||||
if ($parameters['name_second'] !== $account->name['second']) $account->name = ['second' => $parameters['name_second']] + $account->name;
|
||||
if ($parameters['name_last'] !== $account->name['last']) $account->name = ['last' => $parameters['name_last']] + $account->name;
|
||||
if ($parameters['number'] !== $account->number)
|
||||
if (mb_strlen($parameters['number']) === 11) $account->number = $parameters['number'];
|
||||
else throw new exception('Номер должен состоять из 11 символов');
|
||||
if ($parameters['mail'] !== $account->mail) $account->mail = $parameters['mail'];
|
||||
if (!empty($parameters['password']) && !sodium_crypto_pwhash_str_verify($parameters['password'], $account->password) && $password = true)
|
||||
if (mb_strlen($parameters['password']) > 6) $account->password = sodium_crypto_pwhash_str(
|
||||
$parameters['password'],
|
||||
SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
|
||||
SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
|
||||
);
|
||||
else throw new exception('Пароль должен быть длиннее 6 символов');
|
||||
if ($parameters['commentary'] !== $account->commentary) $account->commentary = $parameters['commentary'];
|
||||
// Инициализация параметров (перезапись переданными значениями)
|
||||
if ($parameters['name_first'] !== $account->name['first']) $account->name = ['first' => $parameters['name_first']] + $account->name;
|
||||
if ($parameters['name_second'] !== $account->name['second']) $account->name = ['second' => $parameters['name_second']] + $account->name;
|
||||
if ($parameters['name_last'] !== $account->name['last']) $account->name = ['last' => $parameters['name_last']] + $account->name;
|
||||
if ($parameters['number'] !== $account->number)
|
||||
if (mb_strlen($parameters['number']) === 11) $account->number = $parameters['number'];
|
||||
else throw new exception('Номер должен состоять из 11 символов');
|
||||
if ($parameters['mail'] !== $account->mail) $account->mail = $parameters['mail'];
|
||||
if (!empty($parameters['password']) && !@sodium_crypto_pwhash_str_verify($parameters['password'], $account->password ?? '') && $password = true)
|
||||
if (mb_strlen($parameters['password']) > 6) $account->password = sodium_crypto_pwhash_str(
|
||||
$parameters['password'],
|
||||
SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
|
||||
SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
|
||||
);
|
||||
else throw new exception('Пароль должен быть длиннее 6 символов');
|
||||
if ($parameters['commentary'] !== $account->commentary) $account->commentary = $parameters['commentary'];
|
||||
if (!empty($parameters['transactions']) && $parameters['transactions'] !== $account->transactions)
|
||||
$account->transactions = match ($parameters['transactions']) {
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
default => false
|
||||
};
|
||||
|
||||
if (_core::update($account)) {
|
||||
// Записаны данные аккаунта
|
||||
if (_core::update($account)) {
|
||||
// Записаны данные аккаунта
|
||||
|
||||
if ($account->type === 'worker') {
|
||||
// Сотрудник
|
||||
if ($account->type === 'worker') {
|
||||
// Сотрудник
|
||||
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = registry::workers(
|
||||
before: sprintf(
|
||||
"FILTER a._id == '%s' && a.deleted != true",
|
||||
$account->getId()
|
||||
),
|
||||
after: <<<AQL
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = registry::workers(
|
||||
before: sprintf(
|
||||
"FILTER a._id == '%s' && a.deleted != true",
|
||||
$account->getId()
|
||||
),
|
||||
after: <<<AQL
|
||||
let account = (IS_SAME_COLLECTION('account', a) ? a : b)
|
||||
let worker = (IS_SAME_COLLECTION('worker', a) ? a : b)
|
||||
FILTER account.type == 'worker' && b.deleted != true
|
||||
AQL,
|
||||
amount: 1
|
||||
);
|
||||
} else if ($account->type === 'market') {
|
||||
// Магазин
|
||||
amount: 1
|
||||
);
|
||||
} else if ($account->type === 'market') {
|
||||
// Магазин
|
||||
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = registry::markets(
|
||||
before: sprintf(
|
||||
"FILTER a._id == '%s' && a.deleted != true",
|
||||
$account->getId()
|
||||
),
|
||||
after: <<<AQL
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = registry::markets(
|
||||
before: sprintf(
|
||||
"FILTER a._id == '%s' && a.deleted != true",
|
||||
$account->getId()
|
||||
),
|
||||
after: <<<AQL
|
||||
let account = (IS_SAME_COLLECTION('account', a) ? a : b)
|
||||
let market = (IS_SAME_COLLECTION('market', a) ? a : b)
|
||||
FILTER account.type == 'market' && b.deleted != true
|
||||
AQL,
|
||||
amount: 1
|
||||
);
|
||||
} else {
|
||||
// Администратор или оператор (подразумевается)
|
||||
amount: 1
|
||||
);
|
||||
} else {
|
||||
// Администратор или оператор (подразумевается)
|
||||
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = [['account' => $account->getAll()]];
|
||||
}
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = [['account' => $account->getAll()]];
|
||||
}
|
||||
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'updated' => true,
|
||||
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . $account->type . 's.html'),
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'updated' => true,
|
||||
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . $account->type . 's.html'),
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
if ($password) $return['clipboard'] = <<<TEXT
|
||||
Идентификатор: {$account->getKey()}
|
||||
Пароль: {$parameters['password']}
|
||||
TEXT;
|
||||
if ($password) $return['clipboard'] = match ($account->type) {
|
||||
'worker' => 'Номер: ' . model::worker($account->getId())?->number,
|
||||
'market' => 'Идентификатор: ' . model::market($account->getId())?->id,
|
||||
'operator' => "Идентификатор: {$account->getKey()}",
|
||||
'administrator' => "Идентификатор: {$account->getKey()}",
|
||||
default => "Идентификатор: {$account->getKey()}"
|
||||
}
|
||||
. "\nПароль: {$parameters['password']}";
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'updated' => false,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
@ -192,75 +269,282 @@ final class account extends core
|
|||
*/
|
||||
public function delete(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных аккаунта
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
// Инициализация данных аккаунта
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
|
||||
if (!empty($account)) {
|
||||
// Найден аккаунт
|
||||
|
||||
// Удаление
|
||||
$account->active = false;
|
||||
$account->deleted = true;
|
||||
|
||||
if ($account->type === 'worker') {
|
||||
// Сотрудник
|
||||
|
||||
// Инициализация сотрудника
|
||||
if (empty($worker = model::worker($account->getId()))) throw new exception('Не удалось инициализировать сотрудника');
|
||||
if (!empty($account)) {
|
||||
// Найден аккаунт
|
||||
|
||||
// Удаление
|
||||
$worker->active = false;
|
||||
$worker->deleted = true;
|
||||
$account->active = false;
|
||||
$account->deleted = true;
|
||||
|
||||
// Запись в ArangoDB
|
||||
if (!_core::update($worker)) throw throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else if ($account->type === 'market') {
|
||||
// Магазин
|
||||
if ($account->type === 'worker') {
|
||||
// Сотрудник
|
||||
|
||||
// Инициализация магазина
|
||||
if (empty($market = model::market($account->getId()))) throw new exception('Не удалось инициализировать магазин');
|
||||
// Инициализация сотрудника
|
||||
if (empty($worker = model::worker($account->getId()))) throw new exception('Не удалось инициализировать сотрудника');
|
||||
|
||||
// Удаление
|
||||
$market->active = false;
|
||||
$market->deleted = true;
|
||||
// Удаление
|
||||
$worker->active = false;
|
||||
$worker->deleted = true;
|
||||
|
||||
// Запись в ArangoDB
|
||||
if (!_core::update($market)) throw throw new exception('Не удалось записать изменения в базу данных');
|
||||
}
|
||||
// Запись в ArangoDB
|
||||
if (!_core::update($worker)) throw throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else if ($account->type === 'market') {
|
||||
// Магазин
|
||||
|
||||
if (_core::update($account)) {
|
||||
// Записаны данные аккаунта
|
||||
// Инициализация магазина
|
||||
if (empty($market = model::market($account->getId()))) throw new exception('Не удалось инициализировать магазин');
|
||||
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
// Удаление
|
||||
$market->active = false;
|
||||
$market->deleted = true;
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
// Запись в ArangoDB
|
||||
if (!_core::update($market)) throw throw new exception('Не удалось записать изменения в базу данных');
|
||||
}
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
if (_core::update($account)) {
|
||||
// Записаны данные аккаунта
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode([
|
||||
'deleted' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
]);
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode([
|
||||
'deleted' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
]);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'deleted' => false,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Пометить заблокированным
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function ban(array $parameters = []): ?string
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных аккаунта
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
|
||||
if (!empty($account)) {
|
||||
// Найден аккаунт
|
||||
|
||||
// Блокирование
|
||||
$account->active = false;
|
||||
$account->banned = true;
|
||||
|
||||
if (_core::update($account)) {
|
||||
// Записаны данные аккаунта
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode([
|
||||
'banned' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
]);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'banned' => false,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Снять пометку заблокированного (разблокировать)
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function unban(array $parameters = []): ?string
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных аккаунта
|
||||
$account = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
|
||||
if (!empty($account)) {
|
||||
// Найден аккаунт
|
||||
|
||||
// Блокирование
|
||||
$account->active = true;
|
||||
$account->banned = false;
|
||||
|
||||
if (_core::update($account)) {
|
||||
// Записаны данные аккаунта
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode([
|
||||
'unbanned' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
]);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'unbanned' => false,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ final class administrator extends core
|
|||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у cookie)
|
||||
$value = $_COOKIE["administrators_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters'][$name] ?? 0;
|
||||
$value = $_COOKIE["administrators_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters'][$name] ?? 0;
|
||||
|
||||
// Инициализировано значение?
|
||||
if ($value === null || $value === 0) continue;
|
||||
|
@ -86,7 +86,7 @@ final class administrator extends core
|
|||
// Перебор фильтров статусов (И)
|
||||
|
||||
// Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue)
|
||||
if (empty($value = $_COOKIE["administrators_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters'][$name] ?? 0)) continue;
|
||||
if (empty($value = $_COOKIE["administrators_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters'][$name] ?? 0)) continue;
|
||||
|
||||
// Конвертация ярлыков
|
||||
$converted = match ($name) {
|
||||
|
@ -116,7 +116,7 @@ final class administrator extends core
|
|||
if (!empty($filters_statuses_merged)) $filters .= empty($filters) ? $filters_statuses_merged : " && ($filters_statuses_merged)";
|
||||
|
||||
// Инициализация строки поиска
|
||||
$search = $_COOKIE["administrators_filter_search"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters']['search'] ?? '';
|
||||
$search = $_COOKIE["administrators_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters']['search'] ?? '';
|
||||
if (mb_strlen($search) < 3) $search = null;
|
||||
$search_query = empty($search)
|
||||
? null
|
||||
|
@ -147,8 +147,13 @@ final class administrator extends core
|
|||
$search_query,
|
||||
empty($filters) ? null : " && ($filters)"
|
||||
),
|
||||
after: <<<AQL
|
||||
COLLECT x = account OPTIONS { method: "sorted" }
|
||||
AQL,
|
||||
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['page'],
|
||||
sort: 'x.created DESC, x._key DESC',
|
||||
target: empty($search) ? account::COLLECTION : 'registry_accounts',
|
||||
return: '{account: x}',
|
||||
binds: empty($search) ? [] : [
|
||||
'search' => $search
|
||||
]
|
||||
|
@ -229,12 +234,35 @@ final class administrator extends core
|
|||
if (empty($account)) throw new exception('Не удалось создать аккаунт');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
$this->errors['administrator'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)
|
||||
|
|
|
@ -14,6 +14,9 @@ use mirzaev\ebala\views\templater,
|
|||
// Фреймворк PHP
|
||||
use mirzaev\minimal\controller;
|
||||
|
||||
// Встроенные библиотеки
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Ядро контроллеров
|
||||
*
|
||||
|
@ -53,7 +56,7 @@ class core extends controller
|
|||
public function __construct(bool $initialize = true)
|
||||
{
|
||||
// Блокировка запросов от CloudFlare
|
||||
if ($_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return;
|
||||
if (!empty($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] === 'nginx-ssl early hints') return;
|
||||
|
||||
parent::__construct($initialize);
|
||||
|
||||
|
@ -150,7 +153,7 @@ class core extends controller
|
|||
$buffer = [];
|
||||
|
||||
// Инициализация данных магазина для аккаунта для генерации представления
|
||||
foreach ($this->view->accounts as $vendor) $buffer[] = ['account' => $vendor, 'market' => account::market($vendor->getId(), errors: $this->errors['account'])];
|
||||
foreach ($this->view->accounts as $account) $buffer[] = ['account' => $account, 'market' => account::market($account->getId(), errors: $this->errors['account'])];
|
||||
|
||||
// Запись в глобальную переменную из буфера
|
||||
$this->view->accounts = $buffer;
|
||||
|
|
|
@ -30,7 +30,7 @@ final class index extends core
|
|||
// Перебор фильтров временного промежутка
|
||||
|
||||
// Инициализация значения (приоритет у cookie)
|
||||
if (empty($value = (int) ($_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? (($name === 'from') ? time() : strtotime('+1 month'))))) continue;
|
||||
if (empty($value = (int) ($_COOKIE["tasks_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? (($name === 'from') ? time() : strtotime('+1 month'))))) continue;
|
||||
|
||||
// Генерация значения для аттрибута "value" для HTML-элемента <input>
|
||||
$this->view->{$name} = (int) $value;
|
||||
|
@ -40,7 +40,7 @@ final class index extends core
|
|||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у cookie)
|
||||
$value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? null;
|
||||
$value = $_COOKIE["tasks_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? null;
|
||||
|
||||
// Найдено значение?
|
||||
if ($value === null) continue;
|
||||
|
|
|
@ -44,7 +44,7 @@ final class market extends core
|
|||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у cookie)
|
||||
$value = $_COOKIE["markets_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0;
|
||||
$value = $_COOKIE["markets_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0;
|
||||
|
||||
// Инициализировано значение?
|
||||
if ($value === null || $value === 0) continue;
|
||||
|
@ -94,7 +94,7 @@ final class market extends core
|
|||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue)
|
||||
if (empty($value = $_COOKIE["markets_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0)) continue;
|
||||
if (empty($value = $_COOKIE["markets_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters'][$name] ?? 0)) continue;
|
||||
|
||||
// Конвертация ярлыков
|
||||
$converted = match ($name) {
|
||||
|
@ -135,7 +135,7 @@ final class market extends core
|
|||
if (!empty($filters_statuses_after_merged)) $filters_after .= empty($filters_after) ? $filters_statuses_after_merged : " && ($filters_statuses_after_merged)";
|
||||
|
||||
// Инициализация строки поиска
|
||||
$search = $_COOKIE["markets_filter_search"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters']['search'] ?? '';
|
||||
$search = $_COOKIE["markets_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['markets']['filters']['search'] ?? '';
|
||||
if (mb_strlen($search) < 3) $search = null;
|
||||
$search_query = empty($search)
|
||||
? null
|
||||
|
@ -144,6 +144,7 @@ final class market extends core
|
|||
a.commentary IN TOKENS(@search, 'text_ru')
|
||||
|| a.address IN TOKENS(@search, 'text_ru')
|
||||
|| STARTS_WITH(a._key, @search)
|
||||
|| STARTS_WITH(a.id, @search)
|
||||
|| STARTS_WITH(a.name.first, @search)
|
||||
|| STARTS_WITH(a.name.second, @search)
|
||||
|| STARTS_WITH(a.name.last, @search)
|
||||
|
@ -152,6 +153,7 @@ final class market extends core
|
|||
|| STARTS_WITH(a.number, @search)
|
||||
|| STARTS_WITH(a.mail, @search)
|
||||
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a._key, TOKENS(@search, 'text_en')[0], 2, true))
|
||||
|| (LENGTH(@search) > 2 && LEVENSHTEIN_MATCH(a.id, TOKENS(@search, 'text_en')[0], 1, true))
|
||||
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.first, TOKENS(@search, 'text_ru')[0], 2, true))
|
||||
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.second, TOKENS(@search, 'text_ru')[0], 2, true))
|
||||
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.last, TOKENS(@search, 'text_ru')[0], 2, true))
|
||||
|
@ -178,12 +180,15 @@ final class market extends core
|
|||
FILTER account.type == 'market' && b.deleted != true
|
||||
%s
|
||||
%s
|
||||
COLLECT x = account, y = market OPTIONS { method: "sorted" }
|
||||
AQL,
|
||||
empty($filters_before) ? null : "FILTER $filters_before",
|
||||
empty($filters_after) ? null : "FILTER $filters_after"
|
||||
),
|
||||
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['markets']['page'],
|
||||
sort: 'x.created DESC, y.created DESC, x._key DESC, y._key DESC',
|
||||
target: empty($search) ? account::COLLECTION : 'registry_accounts',
|
||||
return: '{account: x, market: y}',
|
||||
binds: empty($search) ? [] : [
|
||||
'search' => $search
|
||||
]
|
||||
|
@ -236,6 +241,7 @@ final class market extends core
|
|||
else if (!empty($parameters['account_number']) && strlen($parameters['account_number']) < 11) throw new exception('Несоответствие формату SIM-номера аккаунта представителя');
|
||||
else if (!empty($parameters['market_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['market_mail']) === 0) throw new exception('Несоответствие формату почты представителя');
|
||||
else if (!empty($parameters['account_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['account_mail']) === 0) throw new exception('Несоответствие формату почты аккаунта представителя');
|
||||
else if (!empty($parameters['market_id']) && model::read('d.id == "' . $parameters['market_id'] . '"', errors: $this->errors['account']) instanceof _document) throw new exception('Уже существует магазин с данным идентификатором');
|
||||
|
||||
// Универсализация
|
||||
/* $parameters['market_number'] = (int) $parameters['market_number']; */
|
||||
|
@ -274,8 +280,8 @@ final class market extends core
|
|||
];
|
||||
}
|
||||
|
||||
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)
|
||||
$_key = preg_replace('/.+\//', '', $account ?? '');
|
||||
// Инициализация идентификатора магазина
|
||||
$id = empty($parameters['market_id']) ? model::id() : $parameters['market_id'];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
|
@ -289,7 +295,7 @@ final class market extends core
|
|||
echo json_encode(
|
||||
[
|
||||
'clipboard' => empty($this->errors['account']) ? <<<TEXT
|
||||
Идентификатор: $_key
|
||||
Идентификатор: $id
|
||||
Пароль: {$parameters['account_password']}
|
||||
TEXT : '',
|
||||
'errors' => self::parse_only_text($this->errors['account'])
|
||||
|
@ -304,32 +310,38 @@ final class market extends core
|
|||
flush();
|
||||
|
||||
try {
|
||||
// Создание магазина
|
||||
$market = model::create(
|
||||
data: [
|
||||
'name' => [
|
||||
'first' => $parameters['market_name_first'],
|
||||
'second' => $parameters['market_name_second'],
|
||||
'last' => $parameters['market_name_last']
|
||||
if (isset($account)) {
|
||||
// Инициализирован аккаунт
|
||||
|
||||
// Создание магазина
|
||||
$market = model::create(
|
||||
data: [
|
||||
'id' => (string) $id,
|
||||
'name' => [
|
||||
'first' => $parameters['market_name_first'],
|
||||
'second' => $parameters['market_name_second'],
|
||||
'last' => $parameters['market_name_last']
|
||||
],
|
||||
'number' => $parameters['market_number'] === 0 ? '' : $parameters['market_number'],
|
||||
'mail' => $parameters['market_mail'],
|
||||
'type' => $parameters['market_type'],
|
||||
'city' => $parameters['market_city'],
|
||||
'district' => $parameters['market_district'],
|
||||
'address' => $parameters['market_address'],
|
||||
],
|
||||
'number' => $parameters['market_number'] === 0 ? '' : $parameters['market_number'],
|
||||
'mail' => $parameters['market_mail'],
|
||||
'type' => $parameters['market_type'],
|
||||
'city' => $parameters['market_city'],
|
||||
'district' => $parameters['market_district'],
|
||||
'address' => $parameters['market_address'],
|
||||
],
|
||||
errors: $this->errors['account']
|
||||
);
|
||||
errors: $this->errors['account']
|
||||
);
|
||||
|
||||
// Проверка существования созданного магазина
|
||||
if (empty($market)) throw new exception('Не удалось создать магазин');
|
||||
// Проверка существования созданного магазина
|
||||
if (empty($market)) throw new exception('Не удалось создать магазин');
|
||||
|
||||
// Создание ребра: account -> market
|
||||
account::connect($account, $market, 'market', $this->errors['account']);
|
||||
// Создание ребра: account -> market
|
||||
account::connect($account, $market, 'market', $this->errors['account']);
|
||||
}
|
||||
throw new exception('Не инициализирован аккаунт');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
$this->errors['market'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
|
@ -350,7 +362,7 @@ final class market extends core
|
|||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных магазина
|
||||
$market = model::read('d._key == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, type: d.type, city: d.city, district: d.district, address: d.address}')->getAll();
|
||||
$market = model::read('d.id == "' . urldecode($parameters['id']) . '"', return: '{ name: d.name, number: d.number, mail: d.mail, type: d.type, city: d.city, district: d.district, address: d.address}')->getAll();
|
||||
|
||||
if (!empty($market)) {
|
||||
// Найдены данные магазина
|
||||
|
@ -380,51 +392,201 @@ final class market extends core
|
|||
}
|
||||
|
||||
/**
|
||||
* Обновить данные
|
||||
* Заблокировать сотрудника в магазине
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function update(array $parameters = []): ?string
|
||||
public function ban(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
try {
|
||||
if ($this->account->status() && $this->account->type === 'market') {
|
||||
// Авторизован аккаунт магазина
|
||||
|
||||
// Инициализация данных магазина
|
||||
$market = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
// Инициализация данных магазина
|
||||
$market = account::market($this->account->getId());
|
||||
|
||||
if (!empty($market)) {
|
||||
// Найден магазин
|
||||
|
||||
// Инициализация параметров (перезапись переданными значениями)
|
||||
if ($parameters['name_first'] !== $market->name['first']) $market->name = ['first' => $parameters['name_first']] + $market->name;
|
||||
if ($parameters['name_second'] !== $market->name['second']) $market->name = ['second' => $parameters['name_second']] + $market->name;
|
||||
if ($parameters['name_last'] !== $market->name['last']) $market->name = ['last' => $parameters['name_last']] + $market->name;
|
||||
if ($parameters['number'] !== $market->number)
|
||||
if (mb_strlen($parameters['number']) === 11) $market->number = $parameters['number'];
|
||||
else throw new exception('Номер должен состоять из 11 символов');
|
||||
if ($parameters['mail'] !== $market->mail) $market->mail = $parameters['mail'];
|
||||
if ($parameters['type'] !== $market->type) $market->type = $parameters['type'];
|
||||
if ($parameters['city'] !== $market->city) $market->city = $parameters['city'];
|
||||
if ($parameters['district'] !== $market->district) $market->district = $parameters['district'];
|
||||
if ($parameters['address'] !== $market->address) $market->address = $parameters['address'];
|
||||
if ($market instanceof _document) {
|
||||
// Найден магазин
|
||||
|
||||
if (_core::update($market)) {
|
||||
// Записаны данные магазина
|
||||
// Блокировка сотрудника
|
||||
if (!in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = $market->bans + [$parameters['worker']];
|
||||
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = registry::markets(
|
||||
before: sprintf(
|
||||
"FILTER a._id == '%s' && a.deleted != true",
|
||||
$market->getId()
|
||||
),
|
||||
after: <<<AQL
|
||||
let account = (IS_SAME_COLLECTION('account', a) ? a : b)
|
||||
let market = (IS_SAME_COLLECTION('market', a) ? a : b)
|
||||
FILTER account.type == 'market' && b.deleted != true
|
||||
AQL,
|
||||
amount: 1,
|
||||
target: model::COLLECTION
|
||||
);
|
||||
if (_core::update($market)) {
|
||||
// Записаны данные магазина
|
||||
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'banned' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти магазин');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['market'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'banned' => false,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Разблокировать сотрудника в магазине
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function unban(array $parameters = []): ?string
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && $this->account->type === 'market') {
|
||||
// Авторизован аккаунт магазина
|
||||
|
||||
// Инициализация данных магазина
|
||||
$market = account::market($this->account->getId());
|
||||
|
||||
if ($market instanceof _document) {
|
||||
// Найден магазин
|
||||
|
||||
// Разблокировка сотрудника
|
||||
if (in_array($parameters['worker'], $market->bans ??= [], true)) $market->bans = array_diff($market->bans ??= [], [$parameters['worker']]);
|
||||
|
||||
if (_core::update($market)) {
|
||||
// Записаны данные магазина
|
||||
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'unbanned' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти магазин');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['market'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'unbanned' => false,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить наличие сотрудника в реестре заблокированных
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function banned(array $parameters = []): ?string
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && $this->account->type === 'market') {
|
||||
// Авторизован аккаунт магазина
|
||||
|
||||
// Инициализация данных магазина
|
||||
$market = account::market($this->account->getId());
|
||||
|
||||
if ($market instanceof _document) {
|
||||
// Найден магазин
|
||||
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
|
@ -439,8 +601,7 @@ final class market extends core
|
|||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'updated' => true,
|
||||
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'markets.html'),
|
||||
'banned' => in_array($parameters['worker'], $market->bans ?? [], true),
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
|
@ -453,8 +614,155 @@ final class market extends core
|
|||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
} else throw new exception('Не удалось найти магазин');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['market'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить данные
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function update(array $parameters = []): ?string
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных магазина
|
||||
$market = model::read('d.id == "' . urldecode($parameters['id']) . '"');
|
||||
|
||||
if (!empty($market)) {
|
||||
// Найден магазин
|
||||
|
||||
// Инициализация параметров (перезапись переданными значениями)
|
||||
if ($parameters['name_first'] !== $market->name['first']) $market->name = ['first' => $parameters['name_first']] + $market->name;
|
||||
if ($parameters['name_second'] !== $market->name['second']) $market->name = ['second' => $parameters['name_second']] + $market->name;
|
||||
if ($parameters['name_last'] !== $market->name['last']) $market->name = ['last' => $parameters['name_last']] + $market->name;
|
||||
if ($parameters['number'] !== $market->number)
|
||||
if (mb_strlen($parameters['number']) === 11) $market->number = $parameters['number'];
|
||||
else throw new exception('Номер должен состоять из 11 символов');
|
||||
if ($parameters['mail'] !== $market->mail) $market->mail = $parameters['mail'];
|
||||
if ($parameters['type'] !== $market->type) $market->type = $parameters['type'];
|
||||
if ($parameters['city'] !== $market->city) $market->city = $parameters['city'];
|
||||
if ($parameters['district'] !== $market->district) $market->district = $parameters['district'];
|
||||
if ($parameters['address'] !== $market->address) $market->address = $parameters['address'];
|
||||
if (!in_array($parameters['ban'], $market->bans, true)) $market->bans[] = $parameters['ban'];
|
||||
|
||||
if (_core::update($market)) {
|
||||
// Записаны данные магазина
|
||||
|
||||
// Инициализация строки в глобальную переменную шаблонизатора
|
||||
$this->view->rows = registry::markets(
|
||||
before: sprintf(
|
||||
"FILTER a._id == '%s' && a.deleted != true",
|
||||
$market->getId()
|
||||
),
|
||||
after: <<<AQL
|
||||
let account = (IS_SAME_COLLECTION('account', a) ? a : b)
|
||||
let market = (IS_SAME_COLLECTION('market', a) ? a : b)
|
||||
FILTER account.type == 'market' && b.deleted != true
|
||||
AQL,
|
||||
amount: 1,
|
||||
target: model::COLLECTION
|
||||
);
|
||||
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'updated' => true,
|
||||
'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'markets.html'),
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['market'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'updated' => false,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
|
@ -472,7 +780,7 @@ final class market extends core
|
|||
// Авторизован аккаунт оператора или магазина
|
||||
|
||||
// Инициализация данных магазинов
|
||||
$this->view->markets = model::read(filter: 'd.active == true', amount: 10000, return: '{ _key: d._key, name: d.name }');
|
||||
$this->view->markets = model::read(filter: 'd.active == true', amount: 10000, return: '{ id: d.id, name: d.name }');
|
||||
|
||||
// Универсализация
|
||||
if ($this->view->markets instanceof _document) $this->view->markets = [$this->view->markets];
|
||||
|
|
|
@ -38,7 +38,7 @@ final class operator extends core
|
|||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у cookie)
|
||||
$value = $_COOKIE["operators_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters'][$name] ?? 0;
|
||||
$value = $_COOKIE["operators_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters'][$name] ?? 0;
|
||||
|
||||
// Инициализировано значение?
|
||||
if ($value === null || $value === 0) continue;
|
||||
|
@ -86,7 +86,7 @@ final class operator extends core
|
|||
// Перебор фильтров статусов (И)
|
||||
|
||||
// Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue)
|
||||
if (empty($value = $_COOKIE["operators_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters'][$name] ?? 0)) continue;
|
||||
if (empty($value = $_COOKIE["operators_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters'][$name] ?? 0)) continue;
|
||||
|
||||
// Конвертация ярлыков
|
||||
$converted = match ($name) {
|
||||
|
@ -116,7 +116,7 @@ final class operator extends core
|
|||
if (!empty($filters_statuses_merged)) $filters .= empty($filters) ? $filters_statuses_merged : " && ($filters_statuses_merged)";
|
||||
|
||||
// Инициализация строки поиска
|
||||
$search = $_COOKIE["operators_filter_search"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters']['search'] ?? '';
|
||||
$search = $_COOKIE["operators_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters']['search'] ?? '';
|
||||
if (mb_strlen($search) < 3) $search = null;
|
||||
$search_query = empty($search)
|
||||
? null
|
||||
|
@ -147,8 +147,13 @@ final class operator extends core
|
|||
$search_query,
|
||||
empty($filters) ? null : " && ($filters)"
|
||||
),
|
||||
after: <<<AQL
|
||||
COLLECT x = account OPTIONS { method: "sorted" }
|
||||
AQL,
|
||||
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['operators']['page'],
|
||||
sort: 'x.created DESC, x._key DESC',
|
||||
target: empty($search) ? account::COLLECTION : 'registry_accounts',
|
||||
return: '{account: x}',
|
||||
binds: empty($search) ? [] : [
|
||||
'search' => $search
|
||||
]
|
||||
|
@ -228,12 +233,35 @@ final class operator extends core
|
|||
if (empty($account)) throw new exception('Не удалось создать аккаунт');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
$this->errors['operator'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\ebala\controllers;
|
||||
|
||||
// Файлы проекта
|
||||
use mirzaev\ebala\controllers\core,
|
||||
mirzaev\ebala\controllers\traits\errors,
|
||||
mirzaev\ebala\models\payments as model;
|
||||
|
||||
// System libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Контроллер выплат
|
||||
*
|
||||
* @package mirzaev\ebala\controllers
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class payments extends core
|
||||
{
|
||||
use errors;
|
||||
|
||||
/**
|
||||
* Сотрудники
|
||||
*
|
||||
* Расчитать стоимость работы сотрудников за выбранный период и сгенерировать excel-документ
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*
|
||||
* @return void В буфер вывода excel-документ
|
||||
*/
|
||||
public function workers(array $parameters = []): void
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || ($this->account->type === 'operator' && $this->account->transactions))) {
|
||||
// Авторизован аккаунт администратора или оператора (с доступом к транзакциям)
|
||||
|
||||
// Инициализация буфера ошибок
|
||||
$this->errors['export'] ??= [];
|
||||
|
||||
if (!empty($from = (int) ($_COOKIE["tasks_filter_from"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['from']))) {
|
||||
// Инициализирован параметр: from
|
||||
|
||||
if (!empty($to = (int) ($_COOKIE["tasks_filter_to"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['to']))) {
|
||||
// Инициализирован параметр: to
|
||||
|
||||
// Сброс буфера вывода
|
||||
if (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
if (model::workers($from, $to, $this->errors['export'])) {
|
||||
// Сгенерирован excel-документ с выплатами (и отправлен в буфер вывода)
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Description: Spreadsheet transfer');
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment;filename=workers ' . gmdate("d.m.Y", $from) . ' - ' . gmdate("d.m.Y", $to) . '.xlsx');
|
||||
header('Access-Control-Expose-Headers: Content-Disposition');
|
||||
header('Cache-Control: max-age=0');
|
||||
} else throw new exception('Не удалось сгенерировать excel-документ');
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не инициализирован параметр: to');
|
||||
} else throw new exception('Не инициализирован параметр: from');
|
||||
} else throw new exception('Вы не авторизованы');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$this->errors['export'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode(
|
||||
[
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
]
|
||||
);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Магазины
|
||||
*
|
||||
* Расчитать прибыль с магазинов за выбранный период и сгенерировать excel-документ
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*
|
||||
* @return void В буфер вывода excel-документ
|
||||
*/
|
||||
public function markets(array $parameters = []): void
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || ($this->account->type === 'operator' && $this->account->transactions))) {
|
||||
// Авторизован аккаунт администратора или оператора (с доступом к транзакциям)
|
||||
|
||||
// Инициализация буфера ошибок
|
||||
$this->errors['export'] ??= [];
|
||||
|
||||
if (!empty($from = (int) ($_COOKIE["tasks_filter_from"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['from']))) {
|
||||
// Инициализирован параметр: from
|
||||
|
||||
if (!empty($to = (int) ($_COOKIE["tasks_filter_to"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['to']))) {
|
||||
// Инициализирован параметр: to
|
||||
|
||||
// Сброс буфера вывода
|
||||
if (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
if (model::markets($from, $to, $this->errors['export'])) {
|
||||
// Сгенерирован excel-документ с выплатами (и отправлен в буфер вывода)
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Description: Spreadsheet transfer');
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment;filename=markets ' . gmdate("d.m.Y", $from) . ' - ' . gmdate("d.m.Y", $to) . '.xlsx');
|
||||
header('Access-Control-Expose-Headers: Content-Disposition');
|
||||
header('Cache-Control: max-age=0');
|
||||
} else throw new exception('Не удалось сгенерировать excel-документ');
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не инициализирован параметр: to');
|
||||
} else throw new exception('Не инициализирован параметр: from');
|
||||
} else throw new exception('Вы не авторизованы');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$this->errors['export'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode(
|
||||
[
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
]
|
||||
);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Подтвердить
|
||||
*
|
||||
* Подтвердить выполнение операций с документом (магазины или сотрудники)
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function confirm(array $parameters = []): void
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || ($this->account->type === 'operator' && $this->account->transactions))) {
|
||||
// Авторизован аккаунт администратора или оператора (с доступом к транзакциям)
|
||||
|
||||
// Инициализация буфера ошибок
|
||||
$this->errors['confirm'] ??= [];
|
||||
|
||||
if (!empty($from = (int) ($_COOKIE["tasks_filter_from"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['from']))) {
|
||||
// Инициализирован параметр: from
|
||||
|
||||
if (!empty($to = (int) ($_COOKIE["tasks_filter_to"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['to']))) {
|
||||
// Инициализирован параметр: to
|
||||
|
||||
// Запуск процедуры подтверждения
|
||||
model::confirm(
|
||||
$from,
|
||||
$to,
|
||||
match ($parameters['type']) {
|
||||
'workers' => 'workers',
|
||||
'markets' => 'markets',
|
||||
default => throw new exception('Для подтверждения обработки документа необходимо передать его тип: workers, markets')
|
||||
},
|
||||
$this->errors['confirm']
|
||||
);
|
||||
} else throw new exception('Не инициализирован параметр: to');
|
||||
} else throw new exception('Не инициализирован параметр: from');
|
||||
} else throw new exception('Вы не авторизованы');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$this->errors['confirm'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode(
|
||||
[
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
]
|
||||
);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ namespace mirzaev\ebala\controllers;
|
|||
use mirzaev\ebala\controllers\core,
|
||||
mirzaev\ebala\controllers\traits\errors,
|
||||
mirzaev\ebala\models\account,
|
||||
mirzaev\ebala\models\worker,
|
||||
mirzaev\ebala\models\market;
|
||||
|
||||
// Библиотека для ArangoDB
|
||||
|
@ -60,7 +61,7 @@ final class session extends core
|
|||
// Проверка параметров на соответствование требованиям
|
||||
if ($length === 0) throw new exception('Номер не может быть пустым');
|
||||
if ($length !== 11) throw new exception('Номер должен иметь 11 цифр');
|
||||
if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['worker'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
||||
if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['worker'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));
|
||||
|
||||
if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
|
||||
// Запрошено запоминание
|
||||
|
@ -76,11 +77,11 @@ final class session extends core
|
|||
}
|
||||
|
||||
// Поиск аккаунта
|
||||
$account = account::read('d.number == "' . $parameters['worker'] . '"', amount: 1);
|
||||
$worker = worker::read('d.number == "' . $parameters['worker'] . '"', amount: 1);
|
||||
|
||||
// Генерация ответа по запрашиваемым параметрам
|
||||
foreach ($return as $parameter) match ($parameter) {
|
||||
'exist' => $buffer['exist'] = isset($account),
|
||||
'exist' => $buffer['exist'] = isset($worker),
|
||||
'account' => (function () use ($parameters, $remember, &$buffer) {
|
||||
// Запись в буфер сессии
|
||||
if ($remember) $this->session->write(['entry' => ['number' => $parameters['worker']]], $this->errors);
|
||||
|
@ -162,7 +163,7 @@ final class session extends core
|
|||
// Проверка параметров на соответствование требованиям
|
||||
if ($length === 0) throw new exception('Идентификатор аккаунта не может быть пустым');
|
||||
if ($length > 12) throw new exception('Идентификатор аккаунта должен иметь не более 12 цифр');
|
||||
if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['administrator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
||||
if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['administrator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));
|
||||
|
||||
if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
|
||||
// Запрошено запоминание
|
||||
|
@ -268,7 +269,7 @@ final class session extends core
|
|||
// Проверка параметров на соответствование требованиям
|
||||
if ($length === 0) throw new exception('Идентификатор аккаунта не может быть пустым');
|
||||
if ($length > 12) throw new exception('Идентификатор аккаунта должен иметь не более 12 цифр');
|
||||
if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['operator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
||||
if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['operator'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));
|
||||
|
||||
if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
|
||||
// Запрошено запоминание
|
||||
|
@ -363,14 +364,14 @@ final class session extends core
|
|||
|
||||
// Проверка параметров на соответствование требованиям
|
||||
if ($length === 0) throw new exception('Идентификатор аккаунта аккаунта не может быть пустым');
|
||||
if ($length > 40) throw new exception('Идентификатор аккаунта аккаунта должен иметь не более 40 символов');
|
||||
if (preg_match_all('/[^\d\(\)\-\s\r\n\t\0]+/u', $parameters['market'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
||||
if ($length > 3) throw new exception('Идентификатор аккаунта аккаунта должен иметь не более 3 символов');
|
||||
if (preg_match_all('/[^\d]+/u', $parameters['market'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));
|
||||
|
||||
if ($remember = isset($parameters['remember']) && $parameters['remember'] === '1') {
|
||||
// Запрошено запоминание
|
||||
|
||||
// Запись в cookie
|
||||
setcookie('entry__key', $parameters['market'], [
|
||||
setcookie('entry_id', $parameters['market'], [
|
||||
'expires' => strtotime('+1 day'),
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
|
@ -379,15 +380,15 @@ final class session extends core
|
|||
]);
|
||||
}
|
||||
|
||||
// Поиск аккаунта
|
||||
$account = account::read('d._key == "' . $parameters['market'] . '"', amount: 1);
|
||||
// Поиск магазина
|
||||
$market = market::read('d.id == "' . $parameters['market'] . '"', amount: 1);
|
||||
|
||||
// Генерация ответа по запрашиваемым параметрам
|
||||
foreach ($return as $parameter) match ($parameter) {
|
||||
'exist' => $buffer['exist'] = isset($account),
|
||||
'exist' => $buffer['exist'] = isset($market),
|
||||
'account' => (function () use ($parameters, $remember, &$buffer) {
|
||||
// Запись в буфер сессии
|
||||
if ($remember) $this->session->write(['entry' => ['_key' => $parameters['market']]], $this->errors);
|
||||
if ($remember) $this->session->write(['entry' => ['id' => $parameters['market']]], $this->errors);
|
||||
|
||||
// Поиск аккаунта и запись в буфер вывода
|
||||
$buffer['account'] = (new account($this->session, 'market', $this->errors))?->instance() instanceof _document;
|
||||
|
@ -429,7 +430,7 @@ final class session extends core
|
|||
|
||||
// Запись в буфер сессии
|
||||
if (!in_array('account', $return, true) && ($remember ?? false))
|
||||
$this->session->write(['entry' => ['_key' => $parameters['market']]]);
|
||||
$this->session->write(['entry' => ['id' => $parameters['market']]]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -456,7 +457,7 @@ final class session extends core
|
|||
|
||||
// Проверка параметров на соответствование требованиям
|
||||
if ($length > 300) throw new exception('Пароль не может быть длиннее 300 символов');
|
||||
if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['password'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
||||
if (preg_match_all('/[^\w\-\_\d\s]+/u', $parameters['password'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches ?? []));
|
||||
|
||||
// Инициализация значения по умолчанию для типа аккаунта
|
||||
$parameters['type'] ??= 'worker';
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\ebala\controllers;
|
||||
|
||||
// Файлы проекта
|
||||
use mirzaev\ebala\controllers\core,
|
||||
mirzaev\ebala\models\settings as model,
|
||||
mirzaev\ebala\models\core as _core;
|
||||
|
||||
// Библиотека для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// System libraries
|
||||
use datetime,
|
||||
datetimezone,
|
||||
exception;
|
||||
|
||||
/**
|
||||
* Контроллер настроек сайта
|
||||
*
|
||||
* @package mirzaev\ebala\controllers
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class settings extends core
|
||||
{
|
||||
/**
|
||||
* Страница настроек
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function index(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status() && $this->account->type === 'administrator') {
|
||||
// Авторизован аккаунт (администратор)
|
||||
|
||||
// Чтение настроек
|
||||
$this->view->settings = model::search(
|
||||
before: 'FILTER setting.category != null'
|
||||
);
|
||||
|
||||
// Генерация представления
|
||||
$main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'settings.html');
|
||||
|
||||
// Возврат (успех)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]);
|
||||
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main;
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Записать
|
||||
*
|
||||
* Записывает (обновляет) в ArangoDB
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function write(array $parameters = []): void
|
||||
{
|
||||
try {
|
||||
if ($this->account->status() && $this->account->type === 'administrator') {
|
||||
// Авторизован аккаунт администратора
|
||||
|
||||
// Инициализация инстанции настройки
|
||||
$setting = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
|
||||
if ($setting instanceof _document) {
|
||||
// Найдена инстанция настройки
|
||||
|
||||
// Запись значения
|
||||
$setting->value = $parameters['value'];
|
||||
|
||||
if (_core::update($setting)) {
|
||||
// Записано в ArangoDB
|
||||
} else throw new exception('Не удалось обновить заявку');
|
||||
} else throw new exception('Не найдена заявка');
|
||||
} else throw new exception('Вы не авторизованы');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$this->errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\ebala\controllers;
|
||||
|
||||
// Файлы проекта
|
||||
use mirzaev\ebala\controllers\core,
|
||||
mirzaev\ebala\controllers\traits\errors,
|
||||
mirzaev\ebala\models\socket as model,
|
||||
mirzaev\ebala\models\task,
|
||||
mirzaev\ebala\models\session,
|
||||
mirzaev\ebala\models\registry,
|
||||
mirzaev\ebala\models\core as _core;
|
||||
|
||||
// Фреймворк ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
// Библиотека для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Встроенные библиотеки
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Контроллер сокета
|
||||
*
|
||||
* @package mirzaev\ebala\controllers
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class socket extends core
|
||||
{
|
||||
use errors;
|
||||
|
||||
/**
|
||||
* Регистрация
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function registration(array $parameters = []): ?string
|
||||
{
|
||||
try {
|
||||
if (model::session($this->session->getId(), $parameters['key'], $this->errors)) {
|
||||
// Инициализировано соединение
|
||||
|
||||
// @todo сделать например возврат того что соединение удалось и на клиенте что-то оптимизировать (или не удалось чтобы повторил)
|
||||
return null;
|
||||
} else throw new exception('Не удалось зарегистрировать сокет');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['socket'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -29,6 +29,11 @@ final class worker extends core
|
|||
{
|
||||
use errors;
|
||||
|
||||
/**
|
||||
* Типы работ
|
||||
*/
|
||||
final public const WORKS = ['Кассир', 'Выкладчик', 'Гастроном', 'Бригадир', 'Грузчик', 'Мобильный грузчик', 'Мобильный универсал'];
|
||||
|
||||
/**
|
||||
* Главная страница
|
||||
*
|
||||
|
@ -39,12 +44,12 @@ final class worker extends core
|
|||
// Авторизация
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт оператора или администратора
|
||||
|
||||
|
||||
foreach (['active', 'inactive', 'fined', 'decent', 'hided', 'fired'] as $name) {
|
||||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у cookie)
|
||||
$value = $_COOKIE["workers_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? 0;
|
||||
$value = $_COOKIE["workers_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? 0;
|
||||
|
||||
// Инициализировано значение?
|
||||
if ($value === null || $value === 0) continue;
|
||||
|
@ -94,7 +99,7 @@ final class worker extends core
|
|||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue)
|
||||
if (empty($value = $_COOKIE["workers_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? 0)) continue;
|
||||
if (empty($value = $_COOKIE["workers_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters'][$name] ?? 0)) continue;
|
||||
|
||||
// Конвертация ярлыков
|
||||
$converted = match ($name) {
|
||||
|
@ -135,7 +140,7 @@ final class worker extends core
|
|||
if (!empty($filters_statuses_after_merged)) $filters_after .= empty($filters_after) ? $filters_statuses_after_merged : " && ($filters_statuses_after_merged)";
|
||||
|
||||
// Инициализация строки поиска
|
||||
$search = $_COOKIE["workers_filter_search"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters']['search'] ?? '';
|
||||
$search = $_COOKIE["workers_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters']['search'] ?? '';
|
||||
if (mb_strlen($search) < 3) $search = null;
|
||||
$search_query = empty($search)
|
||||
? null
|
||||
|
@ -147,6 +152,7 @@ final class worker extends core
|
|||
|| a.department.address IN TOKENS(@search, 'text_ru')
|
||||
|| a.requisites IN TOKENS(@search, 'text_ru')
|
||||
|| STARTS_WITH(a._key, @search)
|
||||
|| STARTS_WITH(a.id, @search)
|
||||
|| STARTS_WITH(a.name.first, @search)
|
||||
|| STARTS_WITH(a.name.second, @search)
|
||||
|| STARTS_WITH(a.name.last, @search)
|
||||
|
@ -161,6 +167,7 @@ final class worker extends core
|
|||
|| STARTS_WITH(a.requisites, @search)
|
||||
|| STARTS_WITH(a.tax, @search)
|
||||
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a._key, TOKENS(@search, 'text_en')[0], 2, true))
|
||||
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.id, TOKENS(@search, 'text_en')[0], 1, true))
|
||||
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.first, TOKENS(@search, 'text_ru')[0], 2, true))
|
||||
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.second, TOKENS(@search, 'text_ru')[0], 2, true))
|
||||
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.last, TOKENS(@search, 'text_ru')[0], 2, true))
|
||||
|
@ -175,8 +182,7 @@ final class worker extends core
|
|||
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.requisites, TOKENS(@search, 'text_ru')[0], 1, true))
|
||||
|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(a.tax, TOKENS(@search, 'text_ru')[0], 1, true))
|
||||
OPTIONS { collections: ["account", "worker"] }
|
||||
AQL;
|
||||
|
||||
AQL;;
|
||||
// Инициализация данных для генерации HTML-документа с таблицей
|
||||
$this->view->rows = registry::workers(
|
||||
before: sprintf(
|
||||
|
@ -193,12 +199,15 @@ final class worker extends core
|
|||
FILTER account.type == 'worker' && b.deleted != true
|
||||
%s
|
||||
%s
|
||||
COLLECT x = account, y = worker OPTIONS { method: "sorted" }
|
||||
AQL,
|
||||
empty($filters_before) ? null : "FILTER $filters_before",
|
||||
empty($filters_after) ? null : "FILTER $filters_after"
|
||||
),
|
||||
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['workers']['page'],
|
||||
sort: 'x.created DESC, y.created DESC, x._key DESC, y._key DESC',
|
||||
target: empty($search) ? account::COLLECTION : 'registry_accounts',
|
||||
return: '{account: x, worker: y}',
|
||||
binds: empty($search) ? [] : [
|
||||
'search' => $search
|
||||
]
|
||||
|
@ -251,6 +260,7 @@ final class worker extends core
|
|||
else if (!empty($parameters['account_number']) && strlen($parameters['account_number']) < 11) throw new exception('Несоответствие формату SIM-номера аккаунта сотрудника');
|
||||
else if (!empty($parameters['worker_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['worker_mail']) === 0) throw new exception('Несоответствие формату почты сотрудника');
|
||||
else if (!empty($parameters['account_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['account_mail']) === 0) throw new exception('Несоответствие формату почты аккаунта сотрудника');
|
||||
else if (!empty($parameters['worker_id']) && model::read('d.id == "' . $parameters['worker_id'] . '"', errors: $this->errors['account']) instanceof _document) throw new exception('Уже существует сотрудник с данным идентификатором');
|
||||
|
||||
// Универсализация
|
||||
/* $parameters['worker_number'] = (int) $parameters['worker_number']; */
|
||||
|
@ -258,6 +268,7 @@ final class worker extends core
|
|||
if (!empty($parameters['requisites']) && $parameters['worker_requisites'][-1] === '.') $parameters['worker_requisites'] .= '.';
|
||||
if (!empty($parameters['worker_birth'])) $parameters['worker_birth'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_birth'])->getTimestamp();
|
||||
if (!empty($parameters['worker_issued'])) $parameters['worker_issued'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_issued'])->getTimestamp();
|
||||
if (!empty($parameters['works'])) $parameters['works'] = in_array($parameters['works'], static::WORKS) ? $parameters['works'] : static::WORKS[0];
|
||||
if (!empty($parameters['worker_hiring'])) $parameters['worker_hiring'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_hiring'])->getTimestamp();
|
||||
|
||||
// Создание аккаунта
|
||||
|
@ -269,7 +280,7 @@ final class worker extends core
|
|||
'second' => $parameters['account_name_second'],
|
||||
'last' => $parameters['account_name_last']
|
||||
],
|
||||
'number' => $parameters['account_number'] === 0 ? '' : $parameters['account_number'],
|
||||
'number' => $parameters['account_number'],
|
||||
'mail' => $parameters['account_mail'],
|
||||
'password' => sodium_crypto_pwhash_str(
|
||||
$parameters['account_password'],
|
||||
|
@ -293,8 +304,8 @@ final class worker extends core
|
|||
];
|
||||
}
|
||||
|
||||
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)
|
||||
$_key = preg_replace('/.+\//', '', $account ?? '');
|
||||
// Инициализация идентификатора сотрудника
|
||||
$id = empty($parameters['worker_id']) ? model::id() : $parameters['worker_id'];
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
|
@ -308,7 +319,7 @@ final class worker extends core
|
|||
echo json_encode(
|
||||
[
|
||||
'clipboard' => empty($this->errors['account']) ? <<<TEXT
|
||||
Идентификатор: $_key
|
||||
Номер: {$parameters['account_number']}
|
||||
Пароль: {$parameters['account_password']}
|
||||
TEXT : '',
|
||||
'errors' => self::parse_only_text($this->errors['account'])
|
||||
|
@ -323,40 +334,47 @@ final class worker extends core
|
|||
flush();
|
||||
|
||||
try {
|
||||
// Создание сотрудника
|
||||
$worker = model::create(
|
||||
data: [
|
||||
'name' => [
|
||||
'first' => $parameters['worker_name_first'],
|
||||
'second' => $parameters['worker_name_second'],
|
||||
'last' => $parameters['worker_name_last']
|
||||
],
|
||||
'number' => $parameters['worker_number'] === 0 ? '' : $parameters['worker_number'],
|
||||
'mail' => $parameters['worker_mail'],
|
||||
'birth' => $parameters['worker_birth'],
|
||||
'passport' => $parameters['worker_passport'],
|
||||
'issued' => $parameters['worker_issued'],
|
||||
'department' => [
|
||||
'number' => $parameters['worker_department_number'],
|
||||
'address' => $parameters['worker_department_address']
|
||||
],
|
||||
'requisites' => $parameters['worker_requisites'],
|
||||
'payment' => $parameters['worker_payment'],
|
||||
'tax' => $parameters['worker_tax'],
|
||||
'city' => $parameters['worker_city'],
|
||||
'district' => $parameters['worker_district'],
|
||||
'address' => $parameters['worker_address'],
|
||||
'hiring' => $parameters['worker_hiring'],
|
||||
'rating' => 3
|
||||
],
|
||||
errors: $this->errors['account']
|
||||
);
|
||||
if (isset($account)) {
|
||||
// Инициализирован аккаунт
|
||||
|
||||
// Проверка существования созданного сотрудника
|
||||
if (empty($worker)) throw new exception('Не удалось создать сотрудника');
|
||||
// Создание сотрудника
|
||||
$worker = model::create(
|
||||
data: [
|
||||
'id' => (string) $id,
|
||||
'name' => [
|
||||
'first' => $parameters['worker_name_first'],
|
||||
'second' => $parameters['worker_name_second'],
|
||||
'last' => $parameters['worker_name_last']
|
||||
],
|
||||
'number' => $parameters['worker_number'],
|
||||
'mail' => $parameters['worker_mail'],
|
||||
'birth' => $parameters['worker_birth'],
|
||||
'passport' => $parameters['worker_passport'],
|
||||
'issued' => $parameters['worker_issued'],
|
||||
'department' => [
|
||||
'number' => $parameters['worker_department_number'],
|
||||
'address' => $parameters['worker_department_address']
|
||||
],
|
||||
'requisites' => $parameters['worker_requisites'],
|
||||
'payment' => $parameters['worker_payment'],
|
||||
'tax' => $parameters['worker_tax'],
|
||||
'city' => $parameters['worker_city'],
|
||||
'district' => $parameters['worker_district'],
|
||||
'address' => $parameters['worker_address'],
|
||||
'work' => $parameters['worker_work'],
|
||||
'hiring' => $parameters['worker_hiring'],
|
||||
'rating' => 3
|
||||
],
|
||||
errors: $this->errors['account']
|
||||
);
|
||||
|
||||
// Создание ребра: account -> worker
|
||||
account::connect($account, $worker, 'worker', $this->errors['account']);
|
||||
// Проверка существования созданного сотрудника
|
||||
if (empty($worker)) throw new exception('Не удалось создать сотрудника');
|
||||
|
||||
// Создание ребра: account -> worker
|
||||
account::connect($account, $worker, 'worker', $this->errors['account']);
|
||||
}
|
||||
throw new exception('Не инициализирован аккаунт');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$this->errors['account'][] = [
|
||||
|
@ -380,7 +398,7 @@ final class worker extends core
|
|||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных сотрудника
|
||||
$worker = model::read('d._key == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, birth: d.birth, passport: d.passport, issued: d.issued, department: d.department, requisites: d.requisites, payment: d.payment, tax: d.tax, city: d.city, district: d.district, address: d.address, hiring: d.hiring}')->getAll();
|
||||
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"', return: '{ name: d.name, number: d.number, mail: d.mail, birth: d.birth, passport: d.passport, issued: d.issued, department: d.department, requisites: d.requisites, payment: d.payment, tax: d.tax, city: d.city, district: d.district, address: d.address, worl: d.work, hiring: d.hiring, registration: d.registration}')->getAll();
|
||||
|
||||
if (!empty($worker)) {
|
||||
// Найдены данные сотрудника
|
||||
|
@ -420,15 +438,20 @@ final class worker extends core
|
|||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных сотрудника
|
||||
$worker = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"');
|
||||
|
||||
if (!empty($worker)) {
|
||||
// Найден сотрудник
|
||||
|
||||
|
||||
// Универсализация
|
||||
if (!empty($parameters['birth'])) $parameters['birth'] = DateTime::createFromFormat('Y-m-d', $parameters['birth'])->getTimestamp();
|
||||
if (!empty($parameters['issued'])) $parameters['issued'] = DateTime::createFromFormat('Y-m-d', $parameters['issued'])->getTimestamp();
|
||||
if (!empty($parameters['hiring'])) $parameters['hiring'] = DateTime::createFromFormat('Y-m-d', $parameters['hiring'])->getTimestamp();
|
||||
if (!empty($buffer = explode(':', $parameters['works']))) {
|
||||
$parameters['works'] = [];
|
||||
foreach ($buffer ?? [] as $work)
|
||||
if (in_array($work, static::WORKS)) array_push($parameters['works'], $work);
|
||||
}
|
||||
|
||||
// Инициализация параметров (перезапись переданными значениями)
|
||||
if ($parameters['name_first'] !== $worker->name['first']) $worker->name = ['first' => $parameters['name_first']] + $worker->name;
|
||||
|
@ -449,7 +472,9 @@ final class worker extends core
|
|||
if ($parameters['city'] !== $worker->city) $worker->city = $parameters['city'];
|
||||
if ($parameters['district'] !== $worker->district) $worker->district = $parameters['district'];
|
||||
if ($parameters['address'] !== $worker->address) $worker->address = $parameters['address'];
|
||||
if ($parameters['works'] !== $worker->works) $worker->works = $parameters['works'];
|
||||
if ($parameters['hiring'] !== $worker->hiring) $worker->hiring = $parameters['hiring'];
|
||||
if ($parameters['registration'] !== $worker->registration) $worker->registration = $parameters['registration'];
|
||||
|
||||
if (_core::update($worker)) {
|
||||
// Записаны данные сотрудника
|
||||
|
@ -504,6 +529,114 @@ final class worker extends core
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Пометить уволенным
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function fire(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных сотрудника
|
||||
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"');
|
||||
|
||||
if (!empty($worker)) {
|
||||
// Найден сотрудник
|
||||
|
||||
// Увольнение
|
||||
$worker->active = false;
|
||||
$worker->fired = true;
|
||||
|
||||
if (_core::update($worker)) {
|
||||
// Записаны данные сотрудника
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'fired' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Снять пометку уволенного (нанять)
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function hire(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных сотрудника
|
||||
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"');
|
||||
|
||||
if (!empty($worker)) {
|
||||
// Найден сотрудник
|
||||
|
||||
// Увольнение
|
||||
$worker->active = true;
|
||||
$worker->fired = false;
|
||||
|
||||
if (_core::update($worker)) {
|
||||
// Записаны данные сотрудника
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'hired' => true,
|
||||
'errors' => self::parse_only_text($this->errors)
|
||||
];
|
||||
|
||||
// Генерация ответа
|
||||
echo json_encode($return);
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
|
||||
// Отправка и деинициализация буфера вывода
|
||||
ob_end_flush();
|
||||
flush();
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти аккаунт');
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Прочитать данные сотрудников для <datalist>
|
||||
*
|
||||
|
@ -515,7 +648,7 @@ final class worker extends core
|
|||
// Авторизован аккаунт оператора или магазина
|
||||
|
||||
// Инициализация данных сотрудников
|
||||
$this->view->workers = model::read(filter: 'd.active == true', amount: 10000, return: '{ _key: d._key, name: d.name }');
|
||||
$this->view->workers = model::read(filter: 'd.active == true', amount: 10000, return: '{ id: d.id, name: d.name }');
|
||||
|
||||
// Универсализация
|
||||
if ($this->view->workers instanceof _document) $this->view->workers = [$this->view->workers];
|
||||
|
|
|
@ -42,7 +42,7 @@ final class account extends core
|
|||
* Конструктор
|
||||
*
|
||||
* @param ?session $session Инстанция сессии
|
||||
* @param ?string $authenticate Аутентифицировать аккаунт? Если да, то какой категории? ([worker|operator|market] из $_SERVER['INTERFACE'])
|
||||
* @param ?string $authenticate Аутентифицировать аккаунт? Если да, то какой категории? ([worker|market|operator|administrator] из $_SERVER['INTERFACE'])
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return static Инстанция аккаунта
|
||||
|
@ -60,14 +60,21 @@ final class account extends core
|
|||
$this->document = $account->document;
|
||||
|
||||
// Связь сессии с аккаунтом
|
||||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||||
self::session($session->getId(), $this->document->getId(), $errors);
|
||||
|
||||
// Блокировка доступа
|
||||
if ($account?->active !== true) throw new exception('Свяжитесь с оператором');
|
||||
else if ($account?->banned === true) throw new exception('Свяжитесь с оператором');
|
||||
else if ($account->type === 'worker')
|
||||
if (($worker = account::worker($account->getId()))?->active !== true) throw new exception('Свяжитесь с оператором');
|
||||
else if ($worker?->fired === true) throw new exception('Свяжитесь с оператором');
|
||||
|
||||
return $this;
|
||||
} else {
|
||||
// Не найден связанный с сессией аккаунт
|
||||
if (
|
||||
match ($authenticate) {
|
||||
'worker', 'operator', 'market', 'administrator' => true,
|
||||
'worker', 'market', 'operator', 'administrator' => true,
|
||||
default => false
|
||||
}
|
||||
) {
|
||||
|
@ -89,16 +96,21 @@ final class account extends core
|
|||
$this->document = $account;
|
||||
|
||||
// Связь сессии с аккаунтом
|
||||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||||
self::session($session->getId(), $this->document->getId(), $errors);
|
||||
|
||||
// Удаление использованных данных из буфера сессии
|
||||
$session->write(['entry' => ['number' => null, 'password' => null]]);
|
||||
|
||||
// Блокировка доступа
|
||||
if ($account?->active !== true) throw new exception('Свяжитесь с оператором');
|
||||
else if ($account?->banned === true) throw new exception('Свяжитесь с оператором');
|
||||
else if ($account->type === 'worker')
|
||||
if (($worker = account::worker($account->getId()))?->active !== true) throw new exception('Свяжитесь с оператором');
|
||||
else if ($worker?->fired === true) throw new exception('Свяжитесь с оператором');
|
||||
|
||||
// Выход (успех)
|
||||
return $this;
|
||||
} else throw new exception('Неправильный пароль');
|
||||
|
||||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||||
} else throw new exception('Не найден аккаунт');
|
||||
} else throw new exception('Не найден пароль в буфере сессии');
|
||||
} else if (!empty($session->buffer['operator']['entry']['_key'])) {
|
||||
|
@ -117,7 +129,7 @@ final class account extends core
|
|||
$this->document = $account;
|
||||
|
||||
// Связь сессии с аккаунтом
|
||||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||||
self::session($session->getId(), $this->document->getId(), $errors);
|
||||
|
||||
// Удаление использованных данных из буфера сессии
|
||||
$session->write(['entry' => ['_key' => null, 'password' => null]]);
|
||||
|
@ -125,17 +137,15 @@ final class account extends core
|
|||
// Выход (успех)
|
||||
return $this;
|
||||
} else throw new exception('Неправильный пароль');
|
||||
|
||||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||||
}
|
||||
} else throw new exception('Не найден пароль в буфере сессии');
|
||||
} else if (!empty($session->buffer['market']['entry']['_key'])) {
|
||||
} else if (!empty($session->buffer['market']['entry']['id'])) {
|
||||
// Найден идентификатор магазина в буфере сессии
|
||||
|
||||
if (!empty($session->buffer['market']['entry']['password'])) {
|
||||
// Найден пароль в буфере сессии
|
||||
|
||||
if (($account = self::read('d._key == "' . $session->buffer['market']['entry']['_key'] . '" && d.type == "market"', amount: 1)) instanceof _document) {
|
||||
if (($account = market::account(market::read('d.id == "' . $session->buffer['market']['entry']['id'] . '"', amount: 1)?->getId()) ?? null) instanceof _document) {
|
||||
// Найден аккаунт (игнорируются ошибки)
|
||||
|
||||
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['market']['entry']['password'])) {
|
||||
|
@ -145,7 +155,7 @@ final class account extends core
|
|||
$this->document = $account;
|
||||
|
||||
// Связь сессии с аккаунтом
|
||||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||||
self::session($session->getId(), $this->document->getId(), $errors);
|
||||
|
||||
// Удаление использованных данных из буфера сессии
|
||||
$session->write(['entry' => ['_key' => null, 'password' => null]]);
|
||||
|
@ -153,8 +163,6 @@ final class account extends core
|
|||
// Выход (успех)
|
||||
return $this;
|
||||
} else throw new exception('Неправильный пароль');
|
||||
|
||||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||||
}
|
||||
} else throw new exception('Не найден пароль в буфере сессии');
|
||||
} else if (!empty($session->buffer['administrator']['entry'])) {
|
||||
|
@ -173,7 +181,7 @@ final class account extends core
|
|||
$this->document = $account;
|
||||
|
||||
// Связь сессии с аккаунтом
|
||||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||||
self::session($session->getId(), $this->document->getId(), $errors);
|
||||
|
||||
// Удаление использованных данных из буфера сессии
|
||||
$session->write(['entry' => ['_key' => null, 'password' => null]]);
|
||||
|
@ -181,8 +189,6 @@ final class account extends core
|
|||
// Выход (успех)
|
||||
return $this;
|
||||
} else throw new exception('Неправильный пароль');
|
||||
|
||||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||||
}
|
||||
} else throw new exception('Не найден пароль в буфере сессии');
|
||||
} else throw new exception('Не найдены данные первичной идентификации в буфере сессии');
|
||||
|
@ -227,7 +233,7 @@ final class account extends core
|
|||
LIMIT 1
|
||||
RETURN e
|
||||
)
|
||||
FILTER d._id == e[0]._to && d.active == true
|
||||
FILTER d._id == e[0]._to
|
||||
SORT d.created DESC, d._key DESC
|
||||
LIMIT 1
|
||||
RETURN d
|
||||
|
@ -285,8 +291,8 @@ final class account extends core
|
|||
LIMIT 1
|
||||
RETURN e
|
||||
)
|
||||
FILTER d._id == e[0]._to && d.active == true
|
||||
SORT d.created DESC, d._key DESC
|
||||
FILTER d._id == e[0]._to
|
||||
SORT d.created DESC, d.id DESC
|
||||
LIMIT 1
|
||||
RETURN d
|
||||
AQL,
|
||||
|
@ -312,16 +318,19 @@ final class account extends core
|
|||
}
|
||||
|
||||
/**
|
||||
* Инициализировать связь аккаунта с сотрудником
|
||||
* Подключить к сотруднику
|
||||
*
|
||||
* Ищет связь аккаунта с сотрудником, если не находит, то создаёт её
|
||||
*
|
||||
* @param string $worker Идентификатор инстанции документа аккаунта в базе данных
|
||||
* @param string $account Идентификатор инстанции документа аккаунта в базе данных
|
||||
* @param string $target Идентификатор инстанции документа цели в базе данны (подразумевается сотрудник или магазин)
|
||||
* @param string $type Тип подключения (worker|market)
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return bool Связан аккаунт с сотрудником?
|
||||
*
|
||||
* @todo
|
||||
* 1. Переделать на подобие account::session и перенести в mirzaev/ebala/models/worker
|
||||
*/
|
||||
public static function connect(string $account, string $target, string $type = 'worker', array &$errors = []): bool
|
||||
{
|
||||
|
@ -342,11 +351,11 @@ final class account extends core
|
|||
LIMIT 1
|
||||
RETURN d
|
||||
AQL,
|
||||
self::COLLECTION . '_edge_' . worker::COLLECTION,
|
||||
self::COLLECTION . "_edge_$type",
|
||||
$account,
|
||||
$target
|
||||
)) instanceof _document
|
||||
|| $id = document::write(static::$arangodb->session, self::COLLECTION . "_edge_$type", [
|
||||
|| document::write(static::$arangodb->session, self::COLLECTION . "_edge_$type", [
|
||||
'_from' => $account,
|
||||
'_to' => $target
|
||||
])
|
||||
|
@ -381,7 +390,7 @@ final class account extends core
|
|||
{
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, self::COLLECTION))
|
||||
if ($id = document::write(static::$arangodb->session, self::COLLECTION, $data + ['active' => true])) return $id;
|
||||
if ($id = (string) document::write(static::$arangodb->session, self::COLLECTION, $data + ['active' => true])) return $id;
|
||||
else throw new exception('Не удалось создать аккаунт');
|
||||
else throw new exception('Не удалось инициализировать коллекцию');
|
||||
} catch (exception $e) {
|
||||
|
@ -397,6 +406,65 @@ final class account extends core
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подключить к сессии
|
||||
*
|
||||
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
|
||||
*
|
||||
* @param string $session Идентификатор сессии
|
||||
* @param string $account Идентификатор аккаунта
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return bool Аккаунт подключен к сессии?
|
||||
*/
|
||||
public static function session(string $session, string $account, array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
if (
|
||||
collection::init(static::$arangodb->session, self::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, session::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, session::COLLECTION . '_edge_' . self::COLLECTION, true)
|
||||
) {
|
||||
// Инициализированы коллекции
|
||||
|
||||
if (
|
||||
collection::search(static::$arangodb->session, sprintf(
|
||||
<<<AQL
|
||||
FOR document IN %s
|
||||
FILTER document._from == '%s' && document._to == '%s'
|
||||
LIMIT 1
|
||||
RETURN document
|
||||
AQL,
|
||||
session::COLLECTION . '_edge_' . self::COLLECTION,
|
||||
$session,
|
||||
$account
|
||||
)) instanceof _document
|
||||
|| document::write(static::$arangodb->session, session::COLLECTION . '_edge_' . self::COLLECTION, [
|
||||
'_from' => $session,
|
||||
'_to' => $account
|
||||
])
|
||||
) {
|
||||
// Найдено, либо создано ребро: session -> account
|
||||
|
||||
// Возврат (успех)
|
||||
return true;
|
||||
} else throw new exception('Не удалось создать ребро: session -> account');
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$errors['account'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Записать
|
||||
*
|
||||
|
|
|
@ -35,7 +35,7 @@ class core extends model
|
|||
/**
|
||||
* Путь до файла с настройками подключения к базе данных ArangoDB
|
||||
*/
|
||||
final public const ARANGODB = '../settings/arangodb.php';
|
||||
final public const ARANGODB = __DIR__ . '/../settings/arangodb.php';
|
||||
|
||||
/**
|
||||
* Соединение с базой данных ArangoDB
|
||||
|
@ -132,6 +132,146 @@ class core extends model
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect from ArangoDB
|
||||
*
|
||||
* @param string $filter Выражения для фильтрации на языке AQL
|
||||
* @param string $sort Выражение для сортировки на языке AQL
|
||||
* @param int $amount Количество документов для выборки
|
||||
* @param int $page Страница
|
||||
* @param string $index Параметр по которому будет производиться сборка
|
||||
* @param string $return Выражение описываемое возвращаемые данные на языке AQL
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return _document|array|null Массив инстанций документов в базе данных, если найдены
|
||||
*/
|
||||
public static function collect(
|
||||
string $filter = '',
|
||||
string $sort = 'd.created DESC, d._key DESC',
|
||||
int $amount = 1,
|
||||
int $page = 1,
|
||||
string $index = 'd.updated',
|
||||
string $return = 'd',
|
||||
array &$errors = []
|
||||
): _document|array|null {
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, static::COLLECTION)) {
|
||||
// Инициализирована коллекция
|
||||
|
||||
// Exit (success)
|
||||
return collection::search(
|
||||
static::$arangodb->session,
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
FOR d IN %s
|
||||
%s
|
||||
%s
|
||||
LIMIT %d, %d
|
||||
COLLECT index = %s INTO group = %s
|
||||
RETURN { [index]: group }
|
||||
AQL,
|
||||
static::COLLECTION,
|
||||
empty($filter) ? '' : "FILTER $filter",
|
||||
empty($sort) ? '' : "SORT $sort",
|
||||
--$page <= 0 ? 0 : $amount * $page,
|
||||
$amount,
|
||||
$index,
|
||||
$return
|
||||
)
|
||||
);
|
||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Count documents in ArangoDB
|
||||
*
|
||||
* @param ?string $collection Коллекция для подсчёта
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return int|null Количество документов в базе данных, если найдены
|
||||
*/
|
||||
public static function count(?string $collection = null, array &$errors = []): int|null
|
||||
{
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, static::COLLECTION)) {
|
||||
// Инициализирована коллекция
|
||||
|
||||
// Exit (success)
|
||||
return collection::search(
|
||||
static::$arangodb->session,
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
RETURN LENGTH(%s)
|
||||
AQL,
|
||||
$collection ?? static::COLLECTION
|
||||
)
|
||||
);
|
||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate identifier
|
||||
*
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return int Идентиикатор (свободный)
|
||||
*/
|
||||
public static function id(array &$errors = []): int
|
||||
{
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, static::COLLECTION)) {
|
||||
// Инициализирована коллекция
|
||||
|
||||
// Exit (success)
|
||||
return collection::search(
|
||||
static::$arangodb->session,
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
RETURN MAX((FOR d in %s RETURN +d.id))
|
||||
AQL,
|
||||
$collection ?? static::COLLECTION
|
||||
)
|
||||
) + 1;
|
||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete from ArangoDB
|
||||
*
|
||||
|
|
|
@ -0,0 +1,685 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\ebala\models;
|
||||
|
||||
// Файлы проекта
|
||||
use mirzaev\ebala\models\traits\status;
|
||||
|
||||
// Библиотека для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Фреймворк для работы с таблицами
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory,
|
||||
PhpOffice\PhpSpreadsheet\Style\Color,
|
||||
PhpOffice\PhpSpreadsheet\Style\Fill,
|
||||
PhpOffice\PhpSpreadsheet\Style\Conditional,
|
||||
PhpOffice\PhpSpreadsheet\Style\Alignment,
|
||||
PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
// System libraries
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Модель выплат
|
||||
*
|
||||
* @package mirzaev\ebala\models
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class payments extends core
|
||||
{
|
||||
use status;
|
||||
|
||||
/**
|
||||
* Сотрудники
|
||||
*
|
||||
* Расчитать стоимость работы сотрудников за выбранный период и сгенерировать excel-документ
|
||||
*
|
||||
* @param int $from Начальная дата для выборки заявок (unixtime)
|
||||
* @param int $to Конечная дата для выборки заявок (unixtime)
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
* @return bool Записан буфер вывода сгенерированный excel-документ?
|
||||
*/
|
||||
public static function workers(int $from, int $to, array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
// Чтение заявок
|
||||
$tasks = @task::read("d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true && d.result.processed != true", amount: 999999, return: '{worker: d.worker, market: d.market, date: d.date, work: d.work, start: d.start, end: d.end, commentary: d.commentary, rating: d.rating, review: d.review}', errors: $errors);
|
||||
|
||||
if (is_array($tasks) && count($tasks) > 0) {
|
||||
// Найдены заявки
|
||||
|
||||
// Инициализация таблицы
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Конвертация unixtime в читаемую дату
|
||||
$_from = gmdate("d.m.Y", $from);
|
||||
$_to = gmdate("d.m.Y", $to);
|
||||
|
||||
// Запись настроек таблицы
|
||||
$spreadsheet
|
||||
->getProperties()
|
||||
->setCreator('Спецресурс')
|
||||
->setLastModifiedBy('Спецресурс')
|
||||
->setTitle("$_from - $_to")
|
||||
->setSubject("Зарплаты сотрудникам $_from - $_to")
|
||||
->setDescription("Зарплаты сотрудникам за период с $_from по $_to")
|
||||
->setKeywords('зарплата сотрудники');
|
||||
|
||||
// Открытие страницы
|
||||
$spreadsheet->setActiveSheetIndex(0);
|
||||
|
||||
// Запись первой строки (названия колонок)
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->setCellValue('A1', 'Адрес')
|
||||
->setCellValue('B1', 'Дата выплаты')
|
||||
->setCellValue('C1', 'Дата заявки')
|
||||
->setCellValue('D1', 'Магазин')
|
||||
->setCellValue('E1', 'Сотрудник')
|
||||
->setCellValue('F1', 'Работа')
|
||||
->setCellValue('G1', 'Начало')
|
||||
->setCellValue('H1', 'Конец')
|
||||
->setCellValue('I1', 'Часы')
|
||||
->setCellValue('J1', 'Статус')
|
||||
->setCellValue('K1', 'Рейтинг')
|
||||
->setCellValue('L1', 'Отзыв')
|
||||
->setCellValue('M1', 'ФИО')
|
||||
->setCellValue('N1', 'Час')
|
||||
->setCellValue('O1', 'Смена')
|
||||
->setCellValue('P1', 'Штраф')
|
||||
->setCellValue('Q1', 'Премия')
|
||||
->setCellValue('R1', 'Полная оплата')
|
||||
->setCellValue('S1', 'Наличными')
|
||||
->setCellValue('T1', 'Наличные?')
|
||||
->setCellValue('U1', 'Переводом')
|
||||
->setCellValue('V1', 'Реквизиты')
|
||||
->setCellValue('W1', 'Тариф')
|
||||
->setCellValue('X1', 'Без НДС')
|
||||
->setCellValue('Y1', 'Прибыль')
|
||||
->setCellValue('Z1', 'Примечание')
|
||||
->setCellValue('AA1', 'Долг сотрудника')
|
||||
->setCellValue('AB1', 'Кто платит')
|
||||
->setCellValue('AC1', 'Кто платит');
|
||||
|
||||
// Запись цвета верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A1:AC1')
|
||||
->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()
|
||||
->setARGB('ffffffb9');
|
||||
|
||||
// Запись толщины текста верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A1:AC1')
|
||||
->getFont()
|
||||
->setBold(true);
|
||||
|
||||
// Запись размера текста верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A1:AC1')
|
||||
->getFont()
|
||||
->setSize(13);
|
||||
|
||||
// Запись позиции текста верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A1:AC1')
|
||||
->getAlignment()
|
||||
->setHorizontal(Alignment::HORIZONTAL_CENTER)
|
||||
->setVertical(Alignment::VERTICAL_CENTER);
|
||||
|
||||
// Запись ширины строки верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getRowDimension(1)
|
||||
->setRowHeight(24);
|
||||
|
||||
// Запись ширины колонок
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(30);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(18);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(18);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('E')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(22);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('G')->setWidth(12);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('H')->setWidth(12);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('I')->setWidth(12);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('J')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('K')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('L')->setWidth(40);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('M')->setWidth(32);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('N')->setWidth(12);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('O')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('P')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('Q')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('R')->setWidth(16);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('S')->setWidth(16);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('T')->setWidth(22);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('U')->setWidth(22);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('V')->setWidth(80);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('W')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('X')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('Y')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('Z')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('AA')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('AB')->setWidth(14);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('AC')->setWidth(14);
|
||||
|
||||
// Инициализация счётчика строк
|
||||
$row = 2;
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
// Перебор заявок
|
||||
|
||||
// Инициализация сотрудника
|
||||
$worker = worker::read('d.id == "' . $task->worker . '"');
|
||||
|
||||
if ($worker instanceof _document) {
|
||||
// Найден сотрудник
|
||||
|
||||
// Инициализация магазина
|
||||
$market = market::read('d.id == "' . $task->market . '"');
|
||||
|
||||
if ($market instanceof _document) {
|
||||
// Найден магазин
|
||||
|
||||
// Запись строки
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->setCellValue("A$row", $market->city . ' ' . $market->address)
|
||||
->setCellValue("B$row", '')
|
||||
->setCellValue("C$row", gmdate("d.m.Y", $task->date))
|
||||
->setCellValue("D$row", $market->id)
|
||||
->setCellValue("E$row", $worker->id)
|
||||
->setCellValue("F$row", $task->work)
|
||||
->setCellValue("G$row", $task->start)
|
||||
->setCellValue("H$row", $task->end)
|
||||
->setCellValue("I$row", $hours = task::hours($task->start, $task->end, $errors))
|
||||
->setCellValue("J$row", '')
|
||||
->setCellValue("K$row", $task->rating ?? 'Отсутствует')
|
||||
->setCellValue("L$row", $task->review ?? '')
|
||||
->setCellValue("M$row", $worker->name['second'] . ' ' . $worker->name['first'] . ' ' . $worker->name['last'])
|
||||
->setCellValue("N$row", $hour = static::hour('worker', $market->city, $task->work))
|
||||
->setCellValue("O$row", $payment = $hour * $hours)
|
||||
->setCellValue("P$row", ($penalty = static::penalty($task->rating ?? null)) === null ? -$payment : $penalty)
|
||||
->setCellValue("Q$row", $bonus = static::bonus($task->rating ?? null))
|
||||
->setCellValue("R$row", $payment + ($penalty === null ? -$payment : $penalty) + $bonus)
|
||||
->setCellValue("S$row", '')
|
||||
->setCellValue("T$row", $worker->payment) // Наличные?
|
||||
->setCellValue("U$row", '')
|
||||
->setCellValue("V$row", $worker->requisites)
|
||||
->setCellValue("W$row", '')
|
||||
->setCellValue("X$row", '')
|
||||
->setCellValue("Y$row", '')
|
||||
->setCellValue("Z$row", '')
|
||||
->setCellValue("AA$row", '')
|
||||
->setCellValue("AB$row", '')
|
||||
->setCellValue("AC$row", '');
|
||||
|
||||
// Инкрементация счётчика для генерации следующей строки
|
||||
++$row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write to output buffer
|
||||
IOFactory::createWriter($spreadsheet, 'Xlsx')->save('php://output');
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
throw new exception('Не найдены заявки');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Магазины
|
||||
*
|
||||
* Расчитать прибыль с магазинов и сгенерировать excel-документ
|
||||
*
|
||||
* @param int $from Начальная дата для выборки заявок (unixtime)
|
||||
* @param int $to Конечная дата для выборки заявок (unixtime)
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
* @return bool Записан буфер вывода сгенерированный excel-документ?
|
||||
*/
|
||||
public static function markets(int $from, int $to, array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
// Чтение заявок
|
||||
$tasks = @task::collect(
|
||||
"d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true",
|
||||
sort: 'd.date DESC',
|
||||
amount: 999999,
|
||||
index: 'd.date',
|
||||
return: '{worker: d.worker, market: d.market, date: d.date, work: d.work, start: d.start, end: d.end, commentary: d.commentary, rating: d.rating, review: d.review}',
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Универсализация
|
||||
if ($tasks instanceof _document) $tasks = [$tasks];
|
||||
|
||||
// Инициализация буфера объединённых заявок по дате (подразумеваются дни)
|
||||
$merged = [];
|
||||
|
||||
foreach ($tasks as $groups) {
|
||||
// Перебор групп заявок разделённых по датам
|
||||
|
||||
foreach ($groups->getAll() as $date => $_tasks) {
|
||||
// Перебор дат (подразумевается только одна)
|
||||
|
||||
foreach ($_tasks as $task) {
|
||||
// Перебор заявок
|
||||
|
||||
// Первичная инициализация данных в буфере объединённых заявок по дням
|
||||
$merged[$task['market']] ??= [];
|
||||
$merged[$task['market']][$date] ??= [];
|
||||
$merged[$task['market']][$date][$task['work']] ??= ['workers' => 0, 'hours' => 0];
|
||||
|
||||
// Запись в буфер объединённых заявок по дням
|
||||
$merged[$task['market']][$date][$task['work']]['workers']++;
|
||||
$merged[$task['market']][$date][$task['work']]['hours'] += task::hours($task['start'], $task['end'], $errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($merged) > 0) {
|
||||
// Найдены сгенерированные данные
|
||||
|
||||
// Инициализация таблицы
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Конвертация unixtime в читаемую дату
|
||||
$_from = gmdate("d.m.Y", $from);
|
||||
$_to = gmdate("d.m.Y", $to);
|
||||
|
||||
// Запись настроек таблицы
|
||||
$spreadsheet
|
||||
->getProperties()
|
||||
->setCreator('Спецресурс')
|
||||
->setLastModifiedBy('Спецресурс')
|
||||
->setTitle("$_from - $_to")
|
||||
->setSubject(" $_from - $_to")
|
||||
->setDescription(" за период с $_from по $_to")
|
||||
->setKeywords('магазины');
|
||||
|
||||
// Открытие страницы
|
||||
$spreadsheet->setActiveSheetIndex(0);
|
||||
|
||||
// Запись первых строк
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->setCellValue('A1', 'К Договору от 0.0.20')
|
||||
->setCellValue('A2', 'К Договору от 0.0.20')
|
||||
->setCellValue('A4', 'Детализация выполненных заказов за период')
|
||||
->setCellValue('A5', "Период: $_from - $_to")
|
||||
->setCellValue('A6', "Заказчик: ")
|
||||
->setCellValue('A8', "Магазин")
|
||||
->setCellValue('B8', "Тип")
|
||||
->setCellValue('C8', "Адрес")
|
||||
->setCellValue('D8', "Дата")
|
||||
->setCellValue('E8', "Работа")
|
||||
->setCellValue('F8', "Сотрудники")
|
||||
->setCellValue('G8', "Часы")
|
||||
->setCellValue('H8', "Тариф")
|
||||
->setCellValue('I8', "Без НДС")
|
||||
->setCellValue('J8', "С НДС");
|
||||
|
||||
// Запись ширины колонок
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('A')->setWidth(12);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('B')->setWidth(16);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(32);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(16);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('E')->setWidth(16);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(16);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('G')->setWidth(12);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('H')->setWidth(12);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('I')->setWidth(18);
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('J')->setWidth(18);
|
||||
|
||||
// Фиксация верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->freezePane('K9');
|
||||
|
||||
// Объединение ячеек
|
||||
$spreadsheet->getActiveSheet()->mergeCells('A1:J1');
|
||||
$spreadsheet->getActiveSheet()->mergeCells('A2:J2');
|
||||
$spreadsheet->getActiveSheet()->mergeCells('A4:J4');
|
||||
$spreadsheet->getActiveSheet()->mergeCells('A5:J5');
|
||||
$spreadsheet->getActiveSheet()->mergeCells('A6:J6');
|
||||
|
||||
// Запись позиций текстов "к договору"
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A1:J2')
|
||||
->getAlignment()
|
||||
->setHorizontal(Alignment::HORIZONTAL_RIGHT);
|
||||
|
||||
// Запись позиций текста заголовка
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A4:J4')
|
||||
->getAlignment()
|
||||
->setHorizontal(Alignment::HORIZONTAL_CENTER)
|
||||
->setVertical(Alignment::VERTICAL_CENTER);
|
||||
|
||||
// Запись позиций текста верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A8:J8')
|
||||
->getAlignment()
|
||||
->setHorizontal(Alignment::HORIZONTAL_CENTER)
|
||||
->setVertical(Alignment::VERTICAL_CENTER);
|
||||
|
||||
// Запись цвета верхнего колонтинула (левая половина)
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A8:D8')
|
||||
->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()
|
||||
->setARGB('ffdfe4ec');
|
||||
|
||||
// Запись цвета верхнего колонтинула (правая половина)
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('E8:J8')
|
||||
->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()
|
||||
->setARGB('ff8093b3');
|
||||
|
||||
// Запись размера текста верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A8:J8')
|
||||
->getFont()
|
||||
->setSize(12);
|
||||
|
||||
// Запись толщины текста верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle('A8:J8')
|
||||
->getFont()
|
||||
->setBold(true);
|
||||
|
||||
// Запись ширины строки верхнего колонтинула
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getRowDimension(8)
|
||||
->setRowHeight(32);
|
||||
|
||||
// Инициализация счётчика строк
|
||||
$row = 9;
|
||||
|
||||
// Инициализация буфера объединённых данных всех магазинов
|
||||
$total = [
|
||||
'workers' => 0,
|
||||
'hours' => 0,
|
||||
'hour' => [],
|
||||
'payment' => 0,
|
||||
'vat' => 0
|
||||
];
|
||||
|
||||
foreach ($merged as $id => $dates) {
|
||||
// Перебор магазинов
|
||||
|
||||
// Инициализация магазина
|
||||
$market = market::read('d.id == "' . $id . '"');
|
||||
|
||||
if ($market instanceof _document) {
|
||||
// Найден магазин
|
||||
|
||||
// Инициализация буфера объединённых данных магазина
|
||||
$result = [
|
||||
'workers' => 0,
|
||||
'hours' => 0,
|
||||
'hour' => [],
|
||||
'payment' => 0,
|
||||
'vat' => 0
|
||||
];
|
||||
|
||||
foreach ($dates as $date => $works) {
|
||||
// Перебор дат заявок
|
||||
|
||||
foreach ($works as $work => $task) {
|
||||
// Перебор заявок
|
||||
|
||||
// Запись строки с заявками по дате
|
||||
$spreadsheet
|
||||
->setActiveSheetIndex(0)
|
||||
->setCellValue("A$row", $id)
|
||||
->setCellValue("B$row", $market->type)
|
||||
->setCellValue("C$row", $market->address)
|
||||
->setCellValue("D$row", gmdate("d.m.Y", $date))
|
||||
->setCellValue("E$row", $work)
|
||||
->setCellValue("F$row", $task['workers'])
|
||||
->setCellValue("G$row", $task['hours'])
|
||||
->setCellValue("H$row", $hour = static::hour('market', $market->city, $work))
|
||||
->setCellValue("I$row", $payment = $hour * $task['hours'])
|
||||
->setCellValue("J$row", $payment);
|
||||
|
||||
// Запись в буфер объединённых данных магазина
|
||||
$result['workers'] += $task['workers'];
|
||||
$result['hours'] += $task['hours'];
|
||||
$result['hour'][] = $hour;
|
||||
$result['payment'] += $payment;
|
||||
$result['vat'] += $payment;
|
||||
|
||||
// Инкрементация счётчика для генерации следующей строки
|
||||
++$row;
|
||||
}
|
||||
} // Чтение заявок
|
||||
$tasks = @task::collect(
|
||||
"d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true",
|
||||
sort: 'd.date DESC',
|
||||
amount: 999999,
|
||||
index: 'd.date',
|
||||
return: '{worker: d.worker, market: d.market, date: d.date, work: d.work, start: d.start, end: d.end, commentary: d.commentary, rating: d.rating, review: d.review}',
|
||||
errors: $errors
|
||||
);
|
||||
|
||||
// Запись строки с общими данными магазина
|
||||
$spreadsheet
|
||||
->setActiveSheetIndex(0)
|
||||
->setCellValue("A$row", "Всего ($id)")
|
||||
->setCellValue("B$row", '')
|
||||
->setCellValue("C$row", '')
|
||||
->setCellValue("D$row", '')
|
||||
->setCellValue("E$row", '')
|
||||
->setCellValue("F$row", $result['workers'])
|
||||
->setCellValue("G$row", $result['hours'])
|
||||
->setCellValue("H$row", $hour = array_sum($result['hour']) / count($result['hour']))
|
||||
->setCellValue("I$row", $result['payment'])
|
||||
->setCellValue("J$row", $result['vat']);
|
||||
|
||||
// Запись в буфер объединённых данных всех магазинов
|
||||
$total['workers'] += $result['workers'];
|
||||
$total['hours'] += $result['hours'];
|
||||
$total['hour'][] = $hour;
|
||||
$total['payment'] += $result['payment'];
|
||||
$total['vat'] += $result['vat'];
|
||||
|
||||
// Запись цвета строки с общими данными магазина
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle("A$row:J$row")
|
||||
->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()
|
||||
->setARGB('ffdfe4ec');
|
||||
|
||||
++$row;
|
||||
}
|
||||
}
|
||||
|
||||
// Запись строки с общими данными всех магазинов
|
||||
$spreadsheet
|
||||
->setActiveSheetIndex(0)
|
||||
->setCellValue("A$row", "Итого")
|
||||
->setCellValue("B$row", '')
|
||||
->setCellValue("C$row", '')
|
||||
->setCellValue("D$row", '')
|
||||
->setCellValue("E$row", '')
|
||||
->setCellValue("F$row", $total['workers'])
|
||||
->setCellValue("G$row", $total['hours'])
|
||||
->setCellValue("H$row", array_sum($total['hour']) / count($total['hour']))
|
||||
->setCellValue("I$row", $total['payment'])
|
||||
->setCellValue("J$row", $total['vat']);
|
||||
|
||||
// Запись цвета строки с общими данными всех магазинов
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle("A$row:J$row")
|
||||
->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()
|
||||
->setARGB('ffdfe4ec');
|
||||
|
||||
// Запись жирного текста для строки с общими данными всех магазинов
|
||||
$spreadsheet
|
||||
->getActiveSheet()
|
||||
->getStyle("A$row:J$row")
|
||||
->getFont()
|
||||
->setBold(true);
|
||||
|
||||
// Write to output buffer
|
||||
IOFactory::createWriter($spreadsheet, 'Xlsx')->save('php://output');
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
throw new exception('Не найдены заявки');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подтвердить обработку
|
||||
*
|
||||
* Отметить в базе данных то, что выбранные заявки были обработаны
|
||||
*
|
||||
* @param int $from Начальная дата для выборки заявок (unixtime)
|
||||
* @param int $to Конечная дата для выборки заявок (unixtime)
|
||||
* @param string $type Тип документа для подтверждения (workers, markets)
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function confirm(int $from, int $to, string $type, array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
|
||||
// Чтение заявок
|
||||
$tasks = @task::read("d.date >= $from && d.date <= $to && d.problematic == false && d.completed == true && d.result.processed != true", amount: 999999, errors: $errors);
|
||||
|
||||
if (is_array($tasks) && count($tasks) > 0) {
|
||||
// Найдены заявки
|
||||
|
||||
if ($type === 'workers') {
|
||||
// Подтверждена обработка зарплат сотрудников за выбранный период
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
// Перебор заявок
|
||||
|
||||
// Подтверждение того, что заявка обработана (выплачены деньги сотруднику)
|
||||
$task->result = ['processed' => true] + ($task->result ?? []);
|
||||
|
||||
// Запись обновления в базу данных
|
||||
core::update($task);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
throw new exception('Не найдены заявки');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine tariff
|
||||
*
|
||||
* @param string $type Type of tariffs (market, worker)
|
||||
* @param string $city City in which the place of work is located
|
||||
* @param string $work Type of work
|
||||
*
|
||||
* @return int|float Cost of work per hour (rubles)
|
||||
*/
|
||||
public static function hour(string $type, string $city, string $work): int|float
|
||||
{
|
||||
return
|
||||
match (mb_strtolower($type)) {
|
||||
'market', 'магазин' => settings::read("d.category == 'market_hour' && d.city == '$city' && d.work == '$work'")?->value ?? 0,
|
||||
'worker', 'сотрудник' => settings::read("d.category == 'worker_hour' && d.city == '$city' && d.work == '$work'")?->value ?? 0,
|
||||
default => 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Bonus on task
|
||||
*
|
||||
* @param int $rating Rating of the task from the market
|
||||
*
|
||||
* @return int Bonus (rubles)
|
||||
*/
|
||||
public static function bonus(int $rating): int
|
||||
{
|
||||
return settings::read("d.category == 'worker_bonus' && d.rating == $rating")?->value ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Penalty on task
|
||||
*
|
||||
* @param int $rating Rating of the task from the market
|
||||
*
|
||||
* @return int|null Penalty (rubles) (null - all payment)
|
||||
*/
|
||||
public static function penalty(int $rating): ?int
|
||||
{
|
||||
$penalty = settings::read("d.category == 'worker_penalty' && d.rating == $rating")?->value ?? 0;
|
||||
|
||||
return $penalty === 1 ? null : $penalty;
|
||||
}
|
||||
}
|
|
@ -36,7 +36,9 @@ final class registry extends core
|
|||
* @param ?string $after Injection of AQL-code after search of edges
|
||||
* @param int $amount Amount of workers
|
||||
* @param int $page Offset by amount
|
||||
* @param string $sort Sort
|
||||
* @param string $target Collection or view name
|
||||
* @param string $return Data for return
|
||||
* @param array $binds Binds for query
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
|
@ -49,6 +51,7 @@ final class registry extends core
|
|||
int $page = 1,
|
||||
string $sort = 'account.created DESC, account._key DESC',
|
||||
string $target = account::COLLECTION,
|
||||
string $return = '{account, worker}',
|
||||
array $binds = [],
|
||||
array &$errors = []
|
||||
): array {
|
||||
|
@ -69,14 +72,15 @@ final class registry extends core
|
|||
%s
|
||||
SORT %s
|
||||
LIMIT %d, %d
|
||||
RETURN {account, worker}
|
||||
RETURN %s
|
||||
AQL,
|
||||
$target,
|
||||
$before,
|
||||
$after,
|
||||
$sort,
|
||||
--$page <= 0 ? 0 : $amount * $page,
|
||||
$amount
|
||||
$amount,
|
||||
$return
|
||||
), $binds);
|
||||
|
||||
// Exit (success)
|
||||
|
@ -104,6 +108,7 @@ final class registry extends core
|
|||
* @param int $amount Amount of markets
|
||||
* @param int $page Offset by amount
|
||||
* @param string $target Collection or view name
|
||||
* @param string $return Data for return
|
||||
* @param array $binds Binds for query
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
|
@ -116,13 +121,14 @@ final class registry extends core
|
|||
int $page = 1,
|
||||
string $sort = 'account.created DESC, account._key DESC',
|
||||
string $target = account::COLLECTION,
|
||||
string $return = '{account, market}',
|
||||
array $binds = [],
|
||||
array &$errors = []
|
||||
): array {
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, account::COLLECTION) && collection::init(static::$arangodb->session, market::COLLECTION)) {
|
||||
// Инициализированы коллекции
|
||||
|
||||
|
||||
// Search the session data in ArangoDB
|
||||
$markets = collection::search(static::$arangodb->session, sprintf(
|
||||
<<<AQL
|
||||
|
@ -136,14 +142,15 @@ final class registry extends core
|
|||
%s
|
||||
SORT %s
|
||||
LIMIT %d, %d
|
||||
RETURN {account, market}
|
||||
RETURN %s
|
||||
AQL,
|
||||
$target,
|
||||
$before,
|
||||
$after,
|
||||
$sort,
|
||||
--$page <= 0 ? 0 : $amount * $page,
|
||||
$amount
|
||||
$amount,
|
||||
$return
|
||||
), $binds);
|
||||
|
||||
// Exit (success)
|
||||
|
@ -167,9 +174,11 @@ final class registry extends core
|
|||
* Generate operators list
|
||||
*
|
||||
* @param ?string $before Injection of AQL-code before search of edges
|
||||
* @param ?string $after Injection of AQL-code after search of edges
|
||||
* @param int $amount Amount of operators
|
||||
* @param int $page Offset by amount
|
||||
* @param string $target Collection or view name
|
||||
* @param string $return Data for return
|
||||
* @param array $binds Binds for query
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
|
@ -177,10 +186,12 @@ final class registry extends core
|
|||
*/
|
||||
public static function operators(
|
||||
?string $before = '',
|
||||
?string $after = '',
|
||||
int $amount = 100,
|
||||
int $page = 1,
|
||||
string $sort = 'account.created DESC, account._key DESC',
|
||||
string $target = account::COLLECTION,
|
||||
string $return = '{account}',
|
||||
array $binds = [],
|
||||
array &$errors = []
|
||||
): array {
|
||||
|
@ -192,16 +203,19 @@ final class registry extends core
|
|||
$operators = collection::search(static::$arangodb->session, sprintf(
|
||||
<<<AQL
|
||||
FOR account IN %s
|
||||
%s
|
||||
%s
|
||||
SORT %s
|
||||
LIMIT %d, %d
|
||||
RETURN {account}
|
||||
RETURN %s
|
||||
AQL,
|
||||
$target,
|
||||
$before,
|
||||
$after,
|
||||
$sort,
|
||||
--$page <= 0 ? 0 : $amount * $page,
|
||||
$amount
|
||||
$amount,
|
||||
$return
|
||||
), $binds);
|
||||
|
||||
// Exit (success)
|
||||
|
@ -225,9 +239,11 @@ final class registry extends core
|
|||
* Generate administrators list
|
||||
*
|
||||
* @param ?string $before Injection of AQL-code before search of edges
|
||||
* @param ?string $after Injection of AQL-code after search of edges
|
||||
* @param int $amount Amount of administrators
|
||||
* @param int $page Offset by amount
|
||||
* @param string $target Collection or view name
|
||||
* @param string $return Data for return
|
||||
* @param array $binds Binds for query
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
|
@ -235,10 +251,12 @@ final class registry extends core
|
|||
*/
|
||||
public static function administrators(
|
||||
?string $before = '',
|
||||
?string $after = '',
|
||||
int $amount = 100,
|
||||
int $page = 1,
|
||||
string $sort = 'account.created DESC, account._key DESC',
|
||||
string $target = account::COLLECTION,
|
||||
string $return = '{account}',
|
||||
array $binds = [],
|
||||
array &$errors = []
|
||||
): array {
|
||||
|
@ -250,16 +268,19 @@ final class registry extends core
|
|||
$administrators = collection::search(static::$arangodb->session, sprintf(
|
||||
<<<AQL
|
||||
FOR account IN %s
|
||||
%s
|
||||
%s
|
||||
SORT %s
|
||||
LIMIT %d, %d
|
||||
RETURN {account}
|
||||
RETURN %s
|
||||
AQL,
|
||||
$target,
|
||||
$before,
|
||||
$after,
|
||||
$sort,
|
||||
--$page <= 0 ? 0 : $amount * $page,
|
||||
$amount
|
||||
$amount,
|
||||
$return
|
||||
), $binds);
|
||||
|
||||
// Exit (success)
|
||||
|
@ -273,7 +294,6 @@ final class registry extends core
|
|||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
var_dump($errors);
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
|
|
|
@ -164,67 +164,12 @@ final class session extends core
|
|||
// Закрыть сессию
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализировать связь сессии с аккаунтом
|
||||
*
|
||||
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
|
||||
*
|
||||
* @param account $account Инстанция аккаунта
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return bool Связана сессия с аккаунтом?
|
||||
*/
|
||||
public static function connect(string $session, string $account, array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
if (
|
||||
collection::init(static::$arangodb->session, self::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, account::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, self::COLLECTION . '_edge_' . account::COLLECTION, true)
|
||||
) {
|
||||
// Инициализированы коллекции
|
||||
|
||||
if (
|
||||
collection::search(static::$arangodb->session, sprintf(
|
||||
<<<AQL
|
||||
FOR document IN %s
|
||||
FILTER document._from == '%s' && document._to == '%s'
|
||||
LIMIT 1
|
||||
RETURN document
|
||||
AQL,
|
||||
self::COLLECTION . '_edge_' . account::COLLECTION,
|
||||
$session,
|
||||
$account
|
||||
)) instanceof _document
|
||||
|| document::write(static::$arangodb->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
|
||||
'_from' => $session,
|
||||
'_to' => $account
|
||||
])
|
||||
) {
|
||||
// Найдено, либо создано ребро: session -> account
|
||||
|
||||
return true;
|
||||
} else throw new exception('Не удалось создать ребро: session -> account');
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти связанный аккаунт
|
||||
*
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return ?account Инстанция аккаунта, если удалось найти
|
||||
* @return account|null Инстанция аккаунта, если удалось найти
|
||||
*/
|
||||
public function account(array &$errors = []): ?account
|
||||
{
|
||||
|
@ -272,7 +217,8 @@ final class session extends core
|
|||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\ebala\models;
|
||||
|
||||
// Project files
|
||||
use mirzaev\ebala\models\traits\instance,
|
||||
mirzaev\ebala\models\traits\status,
|
||||
mirzaev\ebala\models\account,
|
||||
mirzaev\ebala\models\worker,
|
||||
mirzaev\ebala\models\market;
|
||||
|
||||
// Библиотека для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Фреймворк ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Встроенные библиотеки
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Модель настроек
|
||||
*
|
||||
* Управляет записью и чтением настроек сайта из ArangoDB
|
||||
*
|
||||
* @package mirzaev\ebala\models
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class settings extends core
|
||||
{
|
||||
use instance, status;
|
||||
|
||||
/**
|
||||
* Collection name in ArangoDB
|
||||
*/
|
||||
final public const COLLECTION = 'settings';
|
||||
|
||||
/**
|
||||
* Инстанция документа в базе данных
|
||||
*/
|
||||
protected readonly _document $document;
|
||||
|
||||
/**
|
||||
* Read (search)
|
||||
*
|
||||
* @param ?string $before Injection of AQL-code before search of edges
|
||||
* @param int $amount Amount of administrators
|
||||
* @param int $page Offset by amount
|
||||
* @param string $target Collection or view name
|
||||
* @param array $binds Binds for query
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
* @return array Instances from ArangoDB
|
||||
*/
|
||||
public static function search(
|
||||
?string $before = '',
|
||||
int $amount = 1000,
|
||||
int $page = 1,
|
||||
string $sort = 'setting.category ASC, setting.city ASC, setting.work ASC, setting.rating DESC, setting.created DESC, setting._key DESC',
|
||||
string $target = settings::COLLECTION,
|
||||
array $binds = [],
|
||||
array &$errors = []
|
||||
): array {
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, settings::COLLECTION)) {
|
||||
// Инициализирована коллекция
|
||||
|
||||
// Search the session data in ArangoDB
|
||||
$settings = collection::search(static::$arangodb->session, sprintf(
|
||||
<<<AQL
|
||||
FOR setting IN %s
|
||||
%s
|
||||
SORT %s
|
||||
LIMIT %d, %d
|
||||
LET d = setting.category
|
||||
COLLECT x = setting.category INTO groups
|
||||
RETURN { [x]: groups[*]['setting'] }
|
||||
AQL,
|
||||
$target,
|
||||
$before,
|
||||
$sort,
|
||||
--$page <= 0 ? 0 : $amount * $page,
|
||||
$amount
|
||||
), $binds);
|
||||
|
||||
// Универсализация значений
|
||||
$buffer = [];
|
||||
foreach (is_array($settings) ? $settings : [$settings] as $setting) foreach ($setting->getAll() ?? [] as $category => $data) $buffer[$category] = $data;
|
||||
$settings = $buffer;
|
||||
|
||||
// Exit (success)
|
||||
return $settings;
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
var_dump($errors);
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\ebala\models;
|
||||
|
||||
// Project files
|
||||
use mirzaev\ebala\models\traits\instance,
|
||||
mirzaev\ebala\models\traits\status,
|
||||
mirzaev\ebala\models\account,
|
||||
mirzaev\ebala\models\worker,
|
||||
mirzaev\ebala\models\market;
|
||||
|
||||
// Библиотека для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Фреймворк ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Встроенные библиотеки
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Модель сокета
|
||||
*
|
||||
* Управляет записью и чтением настроек сайта из ArangoDB
|
||||
*
|
||||
* @package mirzaev\ebala\models
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class socket extends core
|
||||
{
|
||||
use instance, status;
|
||||
|
||||
/**
|
||||
* Collection name in ArangoDB
|
||||
*/
|
||||
final public const COLLECTION = 'socket';
|
||||
|
||||
/**
|
||||
* Инстанция документа в базе данных
|
||||
*/
|
||||
protected readonly _document $document;
|
||||
|
||||
/**
|
||||
* Подключение к сессии
|
||||
*
|
||||
* @param string $session Идентификатор сессии
|
||||
* @param string $key Ключ для регистрации (из документа в static::COLLECTION)
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @return bool Сокет подключен к сессии?
|
||||
*/
|
||||
public static function session(string $session, string $key, array &$errors = []): bool
|
||||
{
|
||||
try {
|
||||
if (
|
||||
collection::init(static::$arangodb->session, self::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, session::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, $edge = session::COLLECTION . '_edge_' . self::COLLECTION, true)
|
||||
) {
|
||||
// Инициализирована коллекция
|
||||
|
||||
// Чтение сокета
|
||||
$socket = self::read(
|
||||
filter: "d.key == \"$key\" && d.expires > DATE_NOW() / 1000",
|
||||
sort: 'd.created desc, d.expires desc',
|
||||
amount: 1,
|
||||
);
|
||||
|
||||
if ($socket instanceof _document) {
|
||||
// Найден сокет
|
||||
|
||||
if (document::write(static::$arangodb->session, $edge, [
|
||||
'_from' => $session,
|
||||
'_to' => $socket->getId()
|
||||
])) {
|
||||
// Записано ребро: СЕССИЯ -> СОКЕТ
|
||||
|
||||
// Возврат (успех)
|
||||
return true;
|
||||
} else throw new exception('Не удалось записать изменения в базу данных');
|
||||
} else throw new exception('Не удалось найти сокет');
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors['socket'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти аккаунт
|
||||
*
|
||||
* Ищет аккаунт по идентификатору подключения к сокету
|
||||
*
|
||||
* @param int $socket Идентификатор подключения к сокету (во внутренней базе данных OpenSwoole)
|
||||
* @param array &$errors Реестр ошибок
|
||||
*
|
||||
* @returnaccount|null Аккаунт, если найден
|
||||
*/
|
||||
public static function account(int $socket, array &$errors = []): ?account
|
||||
{
|
||||
try {
|
||||
if (
|
||||
collection::init(static::$arangodb->session, self::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, session::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, account::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, session::COLLECTION . '_edge_' . self::COLLECTION, true)
|
||||
&& collection::init(static::$arangodb->session, session::COLLECTION . '_edge_' . account::COLLECTION, true)
|
||||
) {
|
||||
// Инициализированы коллекции
|
||||
|
||||
// Инициализация инстанции аккаунта
|
||||
$account = new account;
|
||||
|
||||
// Поиск инстанции аккаунта в базе данных
|
||||
$instance = $account->instance(collection::search(static::$arangodb->session, sprintf(
|
||||
<<<AQL
|
||||
FOR a IN 1 OUTBOUND
|
||||
(FOR s IN 1 INBOUND
|
||||
(FOR s IN %s
|
||||
FILTER s.id == %s && s.expires > DATE_NOW() / 1000
|
||||
SORT s.created desc, s.expires desc
|
||||
LIMIT 1
|
||||
RETURN s
|
||||
)[0]._id
|
||||
%s
|
||||
RETURN s
|
||||
)[0]._id
|
||||
%s
|
||||
RETURN a
|
||||
AQL,
|
||||
socket::COLLECTION,
|
||||
$socket,
|
||||
session::COLLECTION . '_edge_' . socket::COLLECTION,
|
||||
session::COLLECTION . '_edge_' . account::COLLECTION,
|
||||
)));
|
||||
|
||||
// Возврат (успех)
|
||||
return $instance instanceof _document ? $account : throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
|
||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||
} catch (exception $e) {
|
||||
// Запись в реестр ошибок
|
||||
$errors['socket'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -14,8 +14,9 @@ use mirzaev\arangodb\collection,
|
|||
// Библиотека для ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Встроенные библиотеки
|
||||
use exception;
|
||||
// System libraries
|
||||
use datetime,
|
||||
exception;
|
||||
|
||||
/**
|
||||
* Модель заданий
|
||||
|
@ -40,7 +41,7 @@ final class task extends core
|
|||
/**
|
||||
* Create task in ArangoDB
|
||||
*
|
||||
* @param ?string $date
|
||||
* @param string|int|null $date
|
||||
* @param ?string $worker
|
||||
* @param ?string $work
|
||||
* @param ?string $start
|
||||
|
@ -51,12 +52,13 @@ final class task extends core
|
|||
* @param bool $hided
|
||||
* @param bool $problematic
|
||||
* @param bool $completed
|
||||
* @param ?string $commentary
|
||||
* @param array $errors
|
||||
*
|
||||
* @return ?string Identificator of instance of ArangoDB
|
||||
*/
|
||||
public static function create(
|
||||
?string $date = null,
|
||||
string|int|null $date = null,
|
||||
?string $worker = null,
|
||||
?string $work = null,
|
||||
?string $start = null,
|
||||
|
@ -67,6 +69,7 @@ final class task extends core
|
|||
bool $hided = false,
|
||||
bool $problematic = false,
|
||||
bool $completed = false,
|
||||
?string $commentary = null,
|
||||
array &$errors = []
|
||||
): ?string {
|
||||
try {
|
||||
|
@ -90,6 +93,7 @@ final class task extends core
|
|||
'hided' => $hided,
|
||||
'problematic' => $problematic,
|
||||
'completed' => $completed,
|
||||
'commentary' => $commentary,
|
||||
]);
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
|
@ -114,6 +118,7 @@ final class task extends core
|
|||
* @param int $amount Amount of tasks
|
||||
* @param int $page Offset by amount
|
||||
* @param string $target Collection or view name
|
||||
* @param string $return Data for return
|
||||
* @param array $binds Binds for query
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
|
@ -129,6 +134,7 @@ final class task extends core
|
|||
int $page = 1,
|
||||
string $sort = 'task.date DESC, task.created DESC, task._key DESC',
|
||||
string $target = self::COLLECTION,
|
||||
string $return = '{task, worker, market}',
|
||||
array $binds = [],
|
||||
array &$errors = []
|
||||
): array {
|
||||
|
@ -145,12 +151,12 @@ final class task extends core
|
|||
<<<AQL
|
||||
FOR task IN %s
|
||||
%s
|
||||
LET worker = (FOR worker in %s FILTER worker._key LIKE task.worker SORT worker.created DESC, worker._key DESC LIMIT 1 RETURN worker)[0]
|
||||
LET market = (FOR market in %s FILTER market._key LIKE task.market SORT market.created DESC, market._key DESC LIMIT 1 RETURN market)[0]
|
||||
LET worker = (FOR worker in %s FILTER worker.id != null && worker.id LIKE task.worker SORT worker.created DESC, worker.id DESC LIMIT 1 RETURN worker)[0]
|
||||
LET market = (FOR market in %s FILTER market.id != null && market.id LIKE task.market SORT market.created DESC, market.id DESC LIMIT 1 RETURN market)[0]
|
||||
%s
|
||||
SORT %s
|
||||
LIMIT %d, %d
|
||||
RETURN {task, worker, market}
|
||||
RETURN %s
|
||||
AQL,
|
||||
$target,
|
||||
$before,
|
||||
|
@ -159,7 +165,8 @@ final class task extends core
|
|||
$after,
|
||||
$sort,
|
||||
--$page <= 0 ? 0 : $amount * $page,
|
||||
$amount
|
||||
$amount,
|
||||
$return
|
||||
), $binds);
|
||||
|
||||
// Exit (success)
|
||||
|
@ -173,11 +180,214 @@ final class task extends core
|
|||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
|
||||
var_dump($errors);
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Посчитать количество часов работы
|
||||
*
|
||||
* @param string $start Начало работы (H:i)
|
||||
* @param string $end Конец работы (H:i)
|
||||
* @param array $errors Errors registry
|
||||
*
|
||||
* @return ?float Количество часов, если удалось расчитать
|
||||
*/
|
||||
public static function hours(string $start, string $end, array &$errors = []): ?float
|
||||
{
|
||||
try {
|
||||
if (
|
||||
!empty($start = datetime::createFromFormat('H:i', (string) $start)) && $start instanceof datetime
|
||||
&& !empty($end = datetime::createFromFormat('H:i', (string) $end)) && $end instanceof datetime
|
||||
) {
|
||||
// Инициализированы $start и $end
|
||||
|
||||
// Расчёт часов работы
|
||||
$hours = (float) $start->diff($end)->format('%R%H.%i');
|
||||
if ($hours < 0) $hours += 24;
|
||||
if ($hours >= 6.5 && $hours < 9) $hours -= 0.5;
|
||||
else if ($hours >= 9 && $hours < 12.5) $hours -= 1;
|
||||
else if ($hours >= 12.5) $hours -= 1.5;
|
||||
|
||||
// Выход (успех)
|
||||
return $hours;
|
||||
}
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Выход (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate work type label in Russian
|
||||
*
|
||||
* @param string $work Type of work
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function label(string $work): string
|
||||
{
|
||||
return match (mb_strtolower($work)) {
|
||||
'cashiers', 'cashier', 'кассиры', 'кассир' => 'Кассир',
|
||||
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 'Выкладчик',
|
||||
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 'Гастроном',
|
||||
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 'Бригадир',
|
||||
'loaders', 'loader', 'грузчики', 'грузчик' => 'Грузчик',
|
||||
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 'Мобильный грузчик',
|
||||
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 'Мобильный универсал',
|
||||
default => $work
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction for work on a task
|
||||
*
|
||||
* @param string $task
|
||||
* @param string $worker
|
||||
* @param int $amount
|
||||
* @param array $errors
|
||||
*
|
||||
* @return ?string Identificator of instance of ArangoDB
|
||||
*/
|
||||
public static function transaction(
|
||||
string $task,
|
||||
string $worker,
|
||||
int|float $amount = 0,
|
||||
array &$errors = []
|
||||
): ?string {
|
||||
try {
|
||||
if (
|
||||
collection::init(static::$arangodb->session, self::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, worker::COLLECTION)
|
||||
&& collection::init(static::$arangodb->session, 'transaction', true)
|
||||
) {
|
||||
// Инициализированы коллекции
|
||||
|
||||
// Запись документа в базу данны и возврат (успех)
|
||||
return document::write(static::$arangodb->session, 'transaction', [
|
||||
'_from' => $task,
|
||||
'_to' => $worker,
|
||||
'amount' => $amount,
|
||||
'processed' => 0,
|
||||
]);
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors[] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block by account
|
||||
*
|
||||
* @param int $task (_key)
|
||||
* @param int $account (_key)
|
||||
* @param array $errors
|
||||
*
|
||||
* @return bool Task is blocked?
|
||||
*/
|
||||
public static function block(
|
||||
int $task,
|
||||
int $account,
|
||||
array &$errors = []
|
||||
): bool {
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, self::COLLECTION)) {
|
||||
// Инициализирована коллекция
|
||||
|
||||
if (($task = static::read('d._key == "' . $task . '"')) instanceof _document) {
|
||||
// Найдена заявка
|
||||
|
||||
if ($task->block === null || $task->block['expires'] < time()) {
|
||||
// Не заблокирована заявка
|
||||
|
||||
// Блокировка
|
||||
$task->block = ['account' => $account, 'expires' => (int) strtotime('+1 minute')];
|
||||
|
||||
// Запись обновления в базу данных и возврат (успех)
|
||||
return core::update($task);
|
||||
} else throw new exception('Заявка уже заблокирована: ' . $task->block['account']);
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors['task'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblock by account
|
||||
*
|
||||
* @param int $task (_key)
|
||||
* @param int $account (_key)
|
||||
* @param array $errors
|
||||
*
|
||||
* @return bool Task is blocked?
|
||||
*/
|
||||
public static function unblock(
|
||||
int $task,
|
||||
int $account,
|
||||
array &$errors = []
|
||||
): bool {
|
||||
try {
|
||||
if (collection::init(static::$arangodb->session, self::COLLECTION)) {
|
||||
// Инициализирована коллекция
|
||||
|
||||
if (($task = static::read('d._key == "' . $task . '"')) instanceof _document) {
|
||||
// Найдена заявка
|
||||
|
||||
if ($task->block !== null) {
|
||||
// Заблокирована заявка
|
||||
|
||||
if ($task->block['account'] === $account || $task->block['expires'] > time()) {
|
||||
// Аккаунт отменяет свою блокировку или истекло время блокировки
|
||||
|
||||
// Разблокировка
|
||||
$task->block = null;
|
||||
|
||||
// Запись обновления в базу данных и возврат (успех)
|
||||
return core::update($task);
|
||||
}
|
||||
} else throw new exception('Заявка не заблокирована');
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} else throw new exception('Не удалось инициализировать коллекции');
|
||||
} catch (exception $e) {
|
||||
// Write to the errors registry
|
||||
$errors['task'][] = [
|
||||
'text' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'stack' => $e->getTrace()
|
||||
];
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
#connection {
|
||||
z-index: 999999;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#connection > i#indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
cursor: help;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
#connection > small {
|
||||
margin-left: 7px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
color: var(--socket-text);
|
||||
}
|
||||
|
||||
#connection > i#indicator.disconnected:not(.connected) {
|
||||
background-color: var(--socket-disconnected);
|
||||
}
|
||||
|
||||
#connection > i#indicator.connected:not(.disconnected) {
|
||||
background-color: var(--socket-connected);
|
||||
}
|
|
@ -17,34 +17,40 @@ section.panel.list.medium {
|
|||
width: 80%;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu {
|
||||
margin-bottom: 10px;%s"
|
||||
section.panel.list> :is(form, search).row.menu {
|
||||
margin-bottom: 10px;
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu > label {
|
||||
section.panel.list> :is(form, search).row.menu>label {
|
||||
height: max-content;
|
||||
min-height: 30px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu > label:not(.solid) {
|
||||
section.panel.list> :is(form, search).row.menu>label:not(.solid) {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu.wide > label {
|
||||
section.panel.list> :is(form, search).row.menu.wide>label {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu.separated {
|
||||
section.panel.list> :is(form, search).row.menu.separated {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
div#popup > section.list > div.row.endless {
|
||||
div#popup>section.list>div.row.endless {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu > label > button {
|
||||
section.panel.list> :is(form, search).row.menu>label>div:has(>button) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
section.panel.list> :is(form, search).row.menu>label>button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -52,14 +58,11 @@ section.panel.list > :is(form, search).row.menu > label > button {
|
|||
height: 30px;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu > label > button.separated {
|
||||
section.panel.list> :is(form, search).row.menu>label>button.separated {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> :is(form, search).row.menu
|
||||
> label
|
||||
> button.separated:before {
|
||||
section.panel.list> :is(form, search).row.menu>label>button.separated:before {
|
||||
content: "";
|
||||
left: -12px;
|
||||
position: absolute;
|
||||
|
@ -68,71 +71,65 @@ section.panel.list
|
|||
border-left: 2px solid var(--earth-above);
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu.stretched > label > button,
|
||||
section.panel.list
|
||||
> :is(form, search).row.menu.stretched
|
||||
> label
|
||||
> input[type="search"] {
|
||||
section.panel.list> :is(form, search).row.menu.stretched>label>button,
|
||||
section.panel.list> :is(form, search).row.menu.stretched>label>input[type="search"] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu.stretched > label > button {
|
||||
section.panel.list> :is(form, search).row.menu.stretched>label>button {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu > label > input {
|
||||
section.panel.list> :is(form, search).row.menu>label>input {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu > label > input:not(.merged) {
|
||||
section.panel.list> :is(form, search).row.menu>label>input:not(.merged) {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
section.panel.list > :is(form, search).row.menu > label > input[type="date"] {
|
||||
section.panel.list> :is(form, search).row.menu>label>input[type="date"] {
|
||||
width: 115px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> :is(form, search).row.menu
|
||||
> label
|
||||
> input[type="search"]
|
||||
+ button {
|
||||
section.panel.list> :is(form, search).row.menu>label>input[type="search"]+button {
|
||||
height: 100%;
|
||||
padding: 0 30px;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
section.panel.list > div#title {
|
||||
section.panel.list>div#title {
|
||||
margin-top: 20px;
|
||||
height: 50px;
|
||||
background-color: var(--background-below-6);
|
||||
}
|
||||
|
||||
section.panel.list > div#title > span {
|
||||
section.panel.list>div#title>span {
|
||||
font-weight: unset;
|
||||
font-size: unset;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
section.panel.list > div.row {
|
||||
section.panel.list>div.row {
|
||||
--width: calc(100% - 24px);
|
||||
--gap: 12px;
|
||||
--background: var(--cloud);
|
||||
position: relative;
|
||||
left: 0px;
|
||||
width: calc(100% - 24px);
|
||||
height: 35px;
|
||||
display: flex;
|
||||
width: var(--width, calc(100% - 24px));
|
||||
gap: var(--gap, 12px);
|
||||
padding: 0 var(--gap, 12px);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)) {
|
||||
section.panel.list>div.row:not(:nth-of-type(1)) {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)) > span {
|
||||
section.panel.list>div.row:not(:nth-of-type(1))>span {
|
||||
height: 100%;
|
||||
line-height: 2.2;
|
||||
padding: 0;
|
||||
|
@ -142,7 +139,7 @@ section.panel.list > div.row:not(:nth-of-type(1)) > span {
|
|||
-moz-box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):is(:hover, :focus) {
|
||||
section.panel.list>div.row:not(:nth-of-type(1)):not([data-blocked]):is(:hover, :focus) {
|
||||
--padding-left: 24px;
|
||||
--padding-right: 24px;
|
||||
left: -12px;
|
||||
|
@ -151,23 +148,23 @@ section.panel.list > div.row:not(:nth-of-type(1)):is(:hover, :focus) {
|
|||
transition: 0s;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:first-of-type {
|
||||
section.panel.list>div.row:first-of-type {
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:last-of-type {
|
||||
section.panel.list>div.row:last-of-type {
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:is(:hover, :focus) * {
|
||||
section.panel.list>div.row:is(:hover, :focus) * {
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1) {
|
||||
section.panel.list>div.row:not(:nth-of-type(1)):nth-child(2n + 1) {
|
||||
--background: var(--cloud-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row[data-selected="true"]:before {
|
||||
section.panel.list>div.row[data-selected="true"]:before {
|
||||
left: -25px;
|
||||
top: 0.08rem;
|
||||
position: absolute;
|
||||
|
@ -186,7 +183,7 @@ section.panel.list > div.row[data-selected="true"]:before {
|
|||
color: var(--interface-brown);
|
||||
}
|
||||
|
||||
section.panel.list > div.row[data-selected="true"]:after {
|
||||
section.panel.list>div.row[data-selected="true"]:after {
|
||||
right: -25px;
|
||||
bottom: 0.08rem;
|
||||
rotate: 180deg;
|
||||
|
@ -206,85 +203,14 @@ section.panel.list > div.row[data-selected="true"]:after {
|
|||
color: var(--interface-brown);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).confirmed {
|
||||
--background: var(--grass);
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(:nth-of-type(1)):nth-child(2n + 1).confirmed {
|
||||
--background: var(--grass-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).published {
|
||||
--background: var(--river);
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(:nth-of-type(1)):nth-child(2n + 1).published {
|
||||
--background: var(--river-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).confirmed.published:not(.problematic) {
|
||||
--background: var(--sea);
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(:nth-of-type(1)).confirmed.published:not(.problematic):nth-child(2n + 1) {
|
||||
--background: var(--sea-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).problematic {
|
||||
--background: var(--clay);
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(:nth-of-type(1)):nth-child(2n + 1).problematic {
|
||||
--background: var(--clay-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).coming {
|
||||
--background: var(--magma);
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(:nth-of-type(1)):nth-child(2n + 1).coming {
|
||||
--background: var(--magma-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).completed:not(.problematic) {
|
||||
--background: var(--sand);
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(:nth-of-type(1)):nth-child(2n + 1).completed:not(.problematic) {
|
||||
--background: var(--sand-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).passed {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).hided * {
|
||||
filter: blur(1px);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).hided:is(:hover, :focus) * {
|
||||
filter: unset;
|
||||
opacity: unset;
|
||||
}
|
||||
|
||||
section.panel.list > div.row.reinitialized {
|
||||
section.panel.list>div.row.reinitialized {
|
||||
animation-duration: 3s;
|
||||
animation-name: row-reinitialized;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(
|
||||
:nth-of-type(1),
|
||||
[data-selected="true"]
|
||||
).reinitializable:before {
|
||||
section.panel.list>div.row:not(:nth-of-type(1),
|
||||
[data-selected="true"]).reinitializable:before {
|
||||
content: attr(data-counter);
|
||||
position: absolute;
|
||||
left: -95px;
|
||||
|
@ -297,16 +223,13 @@ section.panel.list
|
|||
color: var(--earth-text);
|
||||
}
|
||||
|
||||
section.panel.list
|
||||
> div.row:not(:nth-of-type(1), [data-selected="true"]).reinitializable:is(
|
||||
:hover,
|
||||
:focus
|
||||
):before {
|
||||
section.panel.list>div.row:not(:nth-of-type(1), [data-selected="true"]).reinitializable:is(:hover,
|
||||
:focus):before {
|
||||
content: attr(id);
|
||||
color: var(--earth-text-important-below);
|
||||
}
|
||||
|
||||
section.panel.list > div.row > span {
|
||||
section.panel.list>div.row>span {
|
||||
position: relative;
|
||||
margin: auto 0;
|
||||
padding: 8px 0;
|
||||
|
@ -314,52 +237,52 @@ section.panel.list > div.row > span {
|
|||
transition: 0s;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:is(:hover, :focus) > span {
|
||||
section.panel.list>div.row:is(:hover, :focus)>span {
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section.panel.list > div.row > span:not(:first-child) {
|
||||
section.panel.list>div.row>span:not(:first-child) {
|
||||
--padding-left: calc(var(--gap) / 2);
|
||||
}
|
||||
|
||||
section.panel.list > div.row > span:not(:last-child) {
|
||||
section.panel.list>div.row>span:not(:last-child) {
|
||||
--padding-right: calc(var(--gap) / 2);
|
||||
}
|
||||
|
||||
section.panel.list > div.row > span:first-child {
|
||||
section.panel.list>div.row>span:first-child {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
section.panel.list > div.row > span:last-child {
|
||||
section.panel.list>div.row>span:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:hover, :focus) > span:first-child {
|
||||
section.panel.list>div.row:not(:hover, :focus)>span:first-child {
|
||||
--padding-left: var(--gap, 12px);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:hover, :focus) > span:last-child {
|
||||
section.panel.list>div.row:not(:hover, :focus)>span:last-child {
|
||||
--padding-right: var(--gap, 12px);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:nth-of-type(1) > span {
|
||||
section.panel.list>div.row:nth-of-type(1)>span {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:nth-of-type(1) > span > i {
|
||||
section.panel.list>div.row:nth-of-type(1)>span>i {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
section.panel.list > div.row > span[onclick] {
|
||||
section.panel.list>div.row>span[onclick] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
section.panel.list > div.row > span.field {
|
||||
section.panel.list>div.row>span.field {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)) > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row:not(:nth-of-type(1)):not([data-blocked])>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--margin: calc(var(--gap) / 2);
|
||||
--border-left: calc(var(--padding-left, var(--margin, 0px)) * -1);
|
||||
--border-right: var(--padding-right, var(--margin, 0px));
|
||||
|
@ -367,54 +290,222 @@ section.panel.list > div.row:not(:nth-of-type(1)) > span:is(.important, .interac
|
|||
--box-shadow: var(--border-left, 0) 0 0 0 var(--box-shadow-color, var(--background)), var(--border-right, 0) 0 0 0 var(--box-shadow-color, var(--background));
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1) > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).confirmed {
|
||||
--background: var(--grass);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).confirmed {
|
||||
--background: var(--grass-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).published {
|
||||
--background: var(--river);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).published {
|
||||
--background: var(--river-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).confirmed.published:not(.problematic) {
|
||||
--background: var(--sea);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).confirmed.published:not(.problematic):nth-child(2n + 1) {
|
||||
--background: var(--sea-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).problematic {
|
||||
--background: var(--clay);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).problematic {
|
||||
--background: var(--clay-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).coming {
|
||||
--background: var(--magma);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).coming {
|
||||
--background: var(--magma-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).completed:not(.problematic) {
|
||||
--background: var(--sand);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).completed:not(.problematic) {
|
||||
--background: var(--sand-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).passed {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).banned {
|
||||
--background: var(--clay);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).banned a {
|
||||
--color: var(--clay-text);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).banned a:is(:hover, :focus) {
|
||||
--color: var(--clay-text-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).banned a:active {
|
||||
--color: var(--clay-text-below);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).banned {
|
||||
--background: var(--clay-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).banned a {
|
||||
--color: var(--clay-text);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).banned a:is(:hover, :focus) {
|
||||
--color: var(--clay-text-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).banned a:active {
|
||||
--color: var(--clay-text-below);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).fired {
|
||||
--background: var(--magma);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).fired a:is(:hover, :focus) {
|
||||
--color: var(--magma-text-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).fired a:active {
|
||||
--color: var(--magma-text-below);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).fired a {
|
||||
--color: var(--magma-text);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).fired {
|
||||
--background: var(--magma-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).fired a {
|
||||
--color: var(--magma-text);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).fired a:is(:hover, :focus) {
|
||||
--color: var(--magma-text-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).fired a:active {
|
||||
--color: var(--magma-text-below);
|
||||
}
|
||||
|
||||
section.panel.list>div.row:not(:nth-of-type(1)).hided * {
|
||||
filter: blur(1px);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
section.panel.list>div.row:not(:nth-of-type(1)).hided:is(:hover, :focus) * {
|
||||
filter: unset;
|
||||
opacity: unset;
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1)>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--cloud-rainy-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).published > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).published>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--river-deep);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1).published > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).published>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--river-deep-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).confirmed > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).confirmed>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--grass-dense);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1).confirmed > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).confirmed>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--grass-dense-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).confirmed.published:not(.problematic) > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).confirmed.published:not(.problematic)>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--sea-deep);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1).confirmed.published:not(.problematic) > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).confirmed.published:not(.problematic)>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--sea-deep-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).problematic > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).problematic>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--clay-important);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1).problematic > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).problematic>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--clay-important-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).coming > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).coming>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--magma-important);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1).coming > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).coming>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--magma-important-above);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)).completed:not(.problematic) > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)).completed:not(.problematic)>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--sand-important);
|
||||
}
|
||||
|
||||
section.panel.list > div.row:not(:nth-of-type(1)):nth-child(2n + 1).completed:not(.problematic) > span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
section.panel.list>div.row[data-row="task"]:not(:nth-of-type(1)):nth-child(2n + 1).completed:not(.problematic)>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--sand-important-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).banned>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--clay-important);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).banned>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--clay-important-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)).fired>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--magma-important);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-row="worker"]:not(:nth-of-type(1)):nth-child(2n + 1).fired>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--magma-important-above);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-blocked] {
|
||||
margin-left: 8px;
|
||||
width: calc(var(--width) - 16px);
|
||||
cursor: progress;
|
||||
opacity: 70% !important;
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-blocked]:before {
|
||||
content: attr(data-blocked) !important;
|
||||
color: var(--earth-text-important-below);
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-blocked] * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
section.panel.list>div.row:not([data-blocked]):has(+ div.row[data-blocked]) {
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
section.panel.list>div.row[data-blocked]+div.row:not([data-blocked]) {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ input[type="range"] {
|
|||
|
||||
button,
|
||||
input[type="submit"],
|
||||
input[type="range"] {
|
||||
input[type="range"],
|
||||
input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -259,15 +260,16 @@ button:is(.transparent, .transparent:is(:hover, :focus), .transparent:active) {
|
|||
}
|
||||
|
||||
a {
|
||||
color: var(--link);
|
||||
--color: var(--link);
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
a:is(:hover, :focus) {
|
||||
color: var(--link-hover);
|
||||
--color: var(--link-hover);
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: var(--link-active);
|
||||
--color: var(--link-active);
|
||||
transition: unset;
|
||||
}
|
||||
|
||||
|
@ -302,12 +304,14 @@ label * {
|
|||
}
|
||||
|
||||
textarea {
|
||||
--padding-x: 12px;
|
||||
--padding-y: 8px;
|
||||
width: 100%;
|
||||
min-width: calc(100% - 24px);
|
||||
min-height: 120px;
|
||||
max-width: calc(100% - 24px);
|
||||
max-height: 300px;
|
||||
padding: 8px 12px;
|
||||
padding: var(--padding-y, 8px) var(--padding-x, 12px);
|
||||
font-size: smaller;
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
|
|
|
@ -32,12 +32,6 @@ section#administrators.panel.list > div.row > span[data-column="account"] {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
section#administrators.panel.list
|
||||
> div.row:nth-of-type(1)
|
||||
> span[data-column="account"] {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
section#administrators.panel.list > div.row > span[data-column="name"] {
|
||||
min-width: 130px;
|
||||
width: 130px;
|
||||
|
|
|
@ -30,18 +30,22 @@ section#markets.panel.list
|
|||
}
|
||||
|
||||
section#markets.panel.list > div.row > span:is([data-column="account"], [data-column="market"]) {
|
||||
min-width: 102px;
|
||||
width: 102px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
section#markets.panel.list > div.row:nth-of-type(1) > span[data-column="account"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section#workers.panel.list > div.row:nth-of-type(1) > span[data-column="account"] {
|
||||
margin-top: 6px;
|
||||
section#markets.panel.list > div.row > span[data-column="account"] {
|
||||
min-width: 102px;
|
||||
width: 102px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section#workers.panel.list > div.row:nth-of-type(1) > span[data-column="market"] {
|
||||
margin-top: 0px;
|
||||
section#markets.panel.list > div.row > span[data-column="market"] {
|
||||
min-width: 67px;
|
||||
width: 67px;
|
||||
}
|
||||
|
||||
section#markets.panel.list > div.row > span[data-column="name"] {
|
||||
|
|
|
@ -32,10 +32,6 @@ section#operators.panel.list > div.row > span[data-column="account"] {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
section#operators.panel.list > div.row:nth-of-type(1) > span[data-column="account"] {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
section#operators.panel.list > div.row > span[data-column="name"] {
|
||||
min-width: 130px;
|
||||
width: 130px;
|
||||
|
@ -63,3 +59,19 @@ section#operators.panel.list > div.row > span[data-column="commentary"] {
|
|||
min-width: unset;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)).transactions {
|
||||
--background: var(--magma);
|
||||
}
|
||||
|
||||
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)):nth-child(2n + 1).transactions {
|
||||
--background: var(--magma-above);
|
||||
}
|
||||
|
||||
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)).transactions>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--magma-important);
|
||||
}
|
||||
|
||||
section#operators.panel.list>div.row[data-row="operator"]:not(:nth-of-type(1)):nth-child(2n + 1).transactions>span:is(.important, .interactive:is(:hover, :focus)) {
|
||||
--background: var(--magma-important-above);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
main>section#settings.panel {
|
||||
z-index: 1000;
|
||||
width: 800px;
|
||||
position: relative;
|
||||
margin-bottom: calc(15vh - 45px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 60px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 30px 40px;
|
||||
border-radius: 3px;
|
||||
background-color: var(--snow);
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>div {
|
||||
padding-bottom: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>div>div {
|
||||
margin: 0;
|
||||
margin-left: 30px;
|
||||
margin-bottom: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>div>div>h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>div>div>i {
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>div>small {
|
||||
margin-left: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>section.subcategory {
|
||||
padding: 20px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border-radius: 3px;
|
||||
background-color: var(--snow-deep);
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>section.subcategory>h3 {
|
||||
margin: 6px 0px 3px 25px;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>section.subcategory>label>input {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
main>section#settings.panel>section.category>section.subcategory>p.empty {
|
||||
text-align: center;
|
||||
padding: 0 20%;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -13,7 +13,7 @@ section#tasks.panel.list
|
|||
> span:is(
|
||||
[data-column="worker"],
|
||||
[data-column="name"],
|
||||
[data-column="task"],
|
||||
[data-column="work"],
|
||||
[data-column="address"],
|
||||
[data-column="type"],
|
||||
[data-column="tax"],
|
||||
|
@ -34,14 +34,26 @@ section#tasks.panel.list > div.row > span[data-column="date"] {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
section#tasks.panel.list > div.row > span:is([data-column="worker"], [data-column="market"]) {
|
||||
min-width: 102px;
|
||||
width: 102px;
|
||||
section#tasks.panel.list > div.row > span:is([data-column="market"], [data-column="worker"]) {
|
||||
font-weight: bold;
|
||||
}
|
||||
section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="market"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="worker"] {
|
||||
margin-top: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
section#tasks.panel.list > div.row > span[data-column="market"] {
|
||||
min-width: 67px;
|
||||
width: 67px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
section#tasks.panel.list > div.row > span[data-column="worker"] {
|
||||
min-width: 67px;
|
||||
width: 67px;
|
||||
}
|
||||
|
||||
section#tasks.panel.list > div.row > span[data-column="name"] {
|
||||
|
@ -49,14 +61,14 @@ section#tasks.panel.list > div.row > span[data-column="name"] {
|
|||
width: 130px;
|
||||
}
|
||||
|
||||
section#tasks.panel.list > div.row > span[data-column="task"] {
|
||||
section#tasks.panel.list > div.row > span[data-column="work"] {
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
section#tasks.panel.list
|
||||
> div.row:not(:nth-of-type(1))
|
||||
> span[data-column="task"] {
|
||||
> span[data-column="work"] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ section#workers.panel.list
|
|||
[data-column="worker"],
|
||||
[data-column="name"],
|
||||
[data-column="number"],
|
||||
[data-column="mail"],
|
||||
[data-column="work"],
|
||||
[data-column="passport"],
|
||||
[data-column="address"],
|
||||
[data-column="tax"],
|
||||
|
@ -32,18 +32,26 @@ section#workers.panel.list
|
|||
}
|
||||
|
||||
section#workers.panel.list > div.row > span:is([data-column="account"], [data-column="worker"]) {
|
||||
min-width: 102px;
|
||||
width: 102px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section#workers.panel.list > div.row:nth-of-type(1) > span[data-column="account"] {
|
||||
margin-top: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section#workers.panel.list > div.row:nth-of-type(1) > span[data-column="worker"] {
|
||||
margin-top: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
section#workers.panel.list > div.row > span[data-column="account"] {
|
||||
min-width: 102px;
|
||||
width: 102px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section#workers.panel.list > div.row > span[data-column="worker"] {
|
||||
min-width: 67px;
|
||||
width: 67px;
|
||||
}
|
||||
|
||||
section#workers.panel.list > div.row > span[data-column="name"] {
|
||||
|
|
|
@ -38,18 +38,25 @@ div#popup>section.stretched {
|
|||
flex-grow: unset;
|
||||
}
|
||||
|
||||
|
||||
div#popup>section.calculated {
|
||||
width: calc(var(--calculated-width) - var(--padding-horizontal, 0px) * 2);
|
||||
}
|
||||
|
||||
div#popup>section.list {
|
||||
max-width: max(70vw, 1300px);
|
||||
max-height: max(62vh, 600px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 30px;
|
||||
overflow-y: scroll;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
div#popup>section.list.extensive {
|
||||
max-width: unset;
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
div#popup>section.list>h3 {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 22px;
|
||||
|
@ -65,17 +72,30 @@ div#popup>section.list h4 {
|
|||
}
|
||||
|
||||
div#popup>section.list>section.main {
|
||||
--gap: 15px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap, 15px);
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main.flow>div.column:not(:only-child) {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column:not(:only-child)[data-column="buttons"]:last-of-type {
|
||||
margin-left: auto;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
|
||||
div#popup>section.list>section.main>div.column:only-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -121,6 +141,25 @@ div#popup>section.list>section.main>div.column>:is(div, section).row>label>input
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row:has(>label>select[multiple]) {
|
||||
height: auto;
|
||||
max-height: 60px;
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row>label>select[multiple] {
|
||||
height: auto;
|
||||
padding: 0px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row>label>select[multiple]>option {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
/* div#popup>section.list>section.main>div.column> :is(div, select).row>label>select[multiple]>option[selected] {
|
||||
background-color: var(--background-above-5);
|
||||
} */
|
||||
|
||||
div#popup>section.list>section.main>div.column>:is(div, section).row>label> :is(input, button):only-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -140,7 +179,21 @@ div#popup>section.list>section.main>div.column> :is(div, select).row.buttons {
|
|||
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row:not(.buttons, .stretchable, .endless),
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row:not(.buttons, .stretchable, .endless)>button {
|
||||
height: 29px;
|
||||
--height: 29px;
|
||||
height: var(--height, 29px);
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row:not(.buttons .endless).stretchable,
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row:not(.buttons, .endless).stretchable>button {
|
||||
--height: 29px;
|
||||
height: max(var(--height, 29px), fit-content);
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column> :is(div, select).row:not(.buttons .endless).stretchable>textarea {
|
||||
/* min-height: calc(var(--height, 29px) - var(--padding-y, 8ox) * 2); */
|
||||
min-height: 1rem;
|
||||
max-height: 3rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
div#popup>section.list>section.main>div.column>:is(div, section).row:not(.merged)+:is(div, section).row.merged {
|
||||
|
@ -262,3 +315,10 @@ div#popup>section.list>section.main>div.column>section.row.message>textarea+butt
|
|||
div#popup>section.list.errors>section.body>dl>dd {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
div#popup>section.list .separator {
|
||||
border-top: 2px solid var(--separator, var(--cloud));
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
|
@ -83,10 +83,9 @@
|
|||
--sand-important: #d7c06c;
|
||||
--sand-important-below: #dfc79a;
|
||||
|
||||
--magma-text-above: ;
|
||||
--magma-text: ;
|
||||
--magma-text-below: ;
|
||||
--magma-text-below-1: ;
|
||||
--magma-text-above: #111;
|
||||
--magma-text: #5e1a1a;
|
||||
--magma-text-below: #826d1c;
|
||||
--magma-above: #ffd325;
|
||||
--magma: #e6bf26;
|
||||
--magma-below: ;
|
||||
|
@ -168,6 +167,10 @@
|
|||
--link: #3c76ff;
|
||||
--link-hover: #6594ff;
|
||||
--link-active: #3064dd;
|
||||
|
||||
--socket-connected: #2be851;
|
||||
--socket-disconnected: #8e8181;
|
||||
--socket-text: #b09999;
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSans-BoldOblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSans-BoldOblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSans-ExtraLight.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSans-ExtraLight.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansCondensed-Bold.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansCondensed-Bold.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansCondensed-BoldOblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansCondensed-BoldOblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansCondensed-Oblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansCondensed-Oblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansMono-BoldOblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansMono-BoldOblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansMono-Oblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSansMono-Oblique.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerif-BoldItalic.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerif-BoldItalic.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed-Bold.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed-Bold.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed-BoldItalic.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed-BoldItalic.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed-Italic.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed-Italic.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/dejavu/DejaVuLGCSerifCondensed.ttf
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraBoldItalic.woff
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraBoldItalic.woff
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraBoldItalic.woff2
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraBoldItalic.woff2
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraLightItalic.woff
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraLightItalic.woff
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraLightItalic.woff2
Normal file → Executable file
0
mirzaev/ebala/system/public/fonts/fira/FiraSans-ExtraLightItalic.woff2
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue