Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
a5b771a99a | |||
5b10141217 | |||
8c1736f4fd | |||
46ff0a1d9b | |||
cb5abd9358 | |||
efb85a2609 | |||
03391a1269 | |||
26eced8fed | |||
4c6e5cdd1d | |||
c5e940fc25 | |||
8df0254271 | |||
6c4c0b1ada | |||
5641a686a2 | |||
da90b0c533 | |||
db9c99390e | |||
f6d090f250 | |||
4d42e7dca4 |
13
README.md
13
README.md
@@ -1,10 +1,7 @@
|
||||
# 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 **700 000** Russian rubles</br>
|
||||
*As long as commits appear in the repository, this means that i continue paid development*</br>
|
||||
</br>
|
||||
I am selling this site to **capitalist scum** for a lot of money, but you, friend, can use my code **for free** ✌️
|
||||
|
@@ -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)) {
|
||||
// Найдены данные аккаунта
|
||||
@@ -94,7 +126,7 @@ final class account extends core
|
||||
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 (!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,
|
||||
@@ -102,6 +134,12 @@ final class account extends core
|
||||
);
|
||||
else throw new exception('Пароль должен быть длиннее 6 символов');
|
||||
if ($parameters['commentary'] !== $account->commentary) $account->commentary = $parameters['commentary'];
|
||||
if ($parameters['transactions'] !== $account->transactions)
|
||||
$account->transactions = match ($parameters['transactions']) {
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
default => false
|
||||
};
|
||||
|
||||
if (_core::update($account)) {
|
||||
// Записаны данные аккаунта
|
||||
@@ -163,10 +201,14 @@ final class account extends core
|
||||
'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);
|
||||
@@ -264,4 +306,106 @@ final class account extends core
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Пометить заблокированным
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function ban(array $parameters = []): ?string
|
||||
{
|
||||
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('Не удалось найти аккаунт');
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Снять пометку заблокированного (разблокировать)
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function unban(array $parameters = []): ?string
|
||||
{
|
||||
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('Не удалось найти аккаунт');
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
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
|
||||
]
|
||||
|
@@ -14,6 +14,9 @@ use mirzaev\ebala\views\templater,
|
||||
// Фреймворк PHP
|
||||
use mirzaev\minimal\controller;
|
||||
|
||||
// Встроенные библиотеки
|
||||
use exception;
|
||||
|
||||
/**
|
||||
* Ядро контроллеров
|
||||
*
|
||||
@@ -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,29 +310,35 @@ 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'][] = [
|
||||
@@ -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)) {
|
||||
// Найдены данные магазина
|
||||
@@ -379,6 +391,166 @@ final class market extends core
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Заблокировать сотрудника в магазине
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function ban(array $parameters = []): ?string
|
||||
{
|
||||
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 = $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 = [
|
||||
'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('Не удалось найти магазин');
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Разблокировать сотрудника в магазине
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function unban(array $parameters = []): ?string
|
||||
{
|
||||
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('Не удалось найти магазин');
|
||||
}
|
||||
|
||||
// Возврат (провал)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить наличие сотрудника в реестре заблокированных
|
||||
*
|
||||
* @param array $parameters Параметры запроса
|
||||
*/
|
||||
public function banned(array $parameters = []): ?string
|
||||
{
|
||||
if ($this->account->status() && $this->account->type === 'market') {
|
||||
// Авторизован аккаунт магазина
|
||||
|
||||
// Инициализация данных магазина
|
||||
$market = account::market($this->account->getId());
|
||||
|
||||
if ($market instanceof _document) {
|
||||
// Найден магазин
|
||||
|
||||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
|
||||
$this->view->page = null;
|
||||
|
||||
// Запись заголовков ответа
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Encoding: none');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
// Инициализация буфера вывода
|
||||
ob_start();
|
||||
|
||||
// Инициализация буфера ответа
|
||||
$return = [
|
||||
'banned' => in_array($parameters['worker'], $market->bans ?? [], 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('Не удалось найти магазин');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить данные
|
||||
*
|
||||
@@ -390,12 +562,12 @@ final class market extends core
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных магазина
|
||||
$market = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
$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;
|
||||
@@ -407,6 +579,7 @@ final class market extends core
|
||||
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)) {
|
||||
// Записаны данные магазина
|
||||
@@ -472,7 +645,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
|
||||
]
|
||||
|
265
mirzaev/ebala/system/controllers/payments.php
Executable file
265
mirzaev/ebala/system/controllers/payments.php
Executable file
@@ -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';
|
||||
|
94
mirzaev/ebala/system/controllers/settings.php
Executable file
94
mirzaev/ebala/system/controllers/settings.php
Executable file
@@ -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()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
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 = ['Кассир', 'Выкладчик', 'Гастроном', 'Бригадир', 'Грузчик', 'Мобильный грузчик', 'Мобильный универсал'];
|
||||
|
||||
/**
|
||||
* Главная страница
|
||||
*
|
||||
@@ -44,7 +49,7 @@ final class worker extends core
|
||||
// Перебор фильтров статусов
|
||||
|
||||
// Инициализация значения (приоритет у 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,7 +438,7 @@ final class worker extends core
|
||||
// Авторизован аккаунт администратора или оператора
|
||||
|
||||
// Инициализация данных сотрудника
|
||||
$worker = model::read('d._key == "' . $parameters['id'] . '"');
|
||||
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"');
|
||||
|
||||
if (!empty($worker)) {
|
||||
// Найден сотрудник
|
||||
@@ -429,6 +447,11 @@ final class worker extends core
|
||||
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 Инстанция аккаунта
|
||||
@@ -62,12 +62,19 @@ final class account extends core
|
||||
// Связь сессии с аккаунтом
|
||||
session::connect($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
|
||||
}
|
||||
) {
|
||||
@@ -94,11 +101,16 @@ final class account extends core
|
||||
// Удаление использованных данных из буфера сессии
|
||||
$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'])) {
|
||||
@@ -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'])) {
|
||||
@@ -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'])) {
|
||||
@@ -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,
|
||||
@@ -316,7 +322,7 @@ final class account extends core
|
||||
*
|
||||
* Ищет связь аккаунта с сотрудником, если не находит, то создаёт её
|
||||
*
|
||||
* @param string $worker Идентификатор инстанции документа аккаунта в базе данных
|
||||
* @param string $account Идентификатор инстанции документа аккаунта в базе данных
|
||||
* @param string $target Идентификатор инстанции документа цели в базе данны (подразумевается сотрудник или магазин)
|
||||
* @param string $type Тип подключения (worker|market)
|
||||
* @param array &$errors Реестр ошибок
|
||||
@@ -342,11 +348,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 +387,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) {
|
||||
|
@@ -132,6 +132,145 @@ 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
|
||||
*
|
||||
|
685
mirzaev/ebala/system/models/payments.php
Executable file
685
mirzaev/ebala/system/models/payments.php
Executable file
@@ -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,6 +121,7 @@ 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 {
|
||||
@@ -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)
|
||||
|
111
mirzaev/ebala/system/models/settings.php
Executable file
111
mirzaev/ebala/system/models/settings.php
Executable file
@@ -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 [];
|
||||
}
|
||||
}
|
@@ -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,118 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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,54 +71,47 @@ 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 {
|
||||
--gap: 12px;
|
||||
--background: var(--cloud);
|
||||
position: relative;
|
||||
@@ -128,11 +124,11 @@ section.panel.list > div.row {
|
||||
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 +138,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)):is(:hover, :focus) {
|
||||
--padding-left: 24px;
|
||||
--padding-right: 24px;
|
||||
left: -12px;
|
||||
@@ -151,23 +147,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 +182,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 +202,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 +222,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 +236,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))>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 +289,196 @@ 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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
73
mirzaev/ebala/system/public/css/pages/settings.css
Executable file
73
mirzaev/ebala/system/public/css/pages/settings.css
Executable file
@@ -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,29 +34,41 @@ 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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
min-width: 130px;
|
||||
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,20 +32,28 @@ 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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
min-width: 130px;
|
||||
width: 130px;
|
||||
|
@@ -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: ;
|
||||
|
@@ -40,6 +40,7 @@ $router->write('/worker/$worker/read', 'task', 'worker', 'POST');
|
||||
$router->write('/worker/$id/fields', 'worker', 'fields', 'POST');
|
||||
$router->write('/worker/$id/update', 'worker', 'update', 'POST');
|
||||
$router->write('/worker/$id/fire', 'worker', 'fire', 'POST');
|
||||
$router->write('/worker/$id/hire', 'worker', 'hire', 'POST');
|
||||
$router->write('/workers', 'worker', 'index', 'GET');
|
||||
$router->write('/workers', 'worker', 'index', 'POST');
|
||||
$router->write('/workers/read', 'worker', 'read', 'POST');
|
||||
@@ -48,6 +49,9 @@ $router->write('/workers/create', 'worker', 'create', 'POST');
|
||||
$router->write('/market/$market/read', 'task', 'market', 'POST');
|
||||
$router->write('/market/$id/fields', 'market', 'fields', 'POST');
|
||||
$router->write('/market/$id/update', 'market', 'update', 'POST');
|
||||
$router->write('/market/ban/$worker', 'market', 'ban', 'POST');
|
||||
$router->write('/market/unban/$worker', 'market', 'unban', 'POST');
|
||||
$router->write('/market/banned/$worker', 'market', 'banned', 'POST');
|
||||
$router->write('/markets', 'market', 'index', 'GET');
|
||||
$router->write('/markets', 'market', 'index', 'POST');
|
||||
$router->write('/markets/read', 'market', 'read', 'POST');
|
||||
@@ -61,13 +65,13 @@ $router->write('/administrators', 'administrator', 'index', 'GET');
|
||||
$router->write('/administrators', 'administrator', 'index', 'POST');
|
||||
$router->write('/administrators/read', 'administrator', 'read', 'POST');
|
||||
$router->write('/administrators/create', 'administrator', 'create', 'POST');
|
||||
$router->write('/settings', 'settings', 'index', 'GET');
|
||||
$router->write('/settings', 'settings', 'index', 'POST');
|
||||
$router->write('/$id', 'account', 'index', 'GET');
|
||||
$router->write('/$id', 'account', 'index', 'POST');
|
||||
$router->write('/$id/fields', 'account', 'fields', 'POST');
|
||||
$router->write('/$id/update', 'account', 'update', 'POST');
|
||||
$router->write('/$id/delete', 'account', 'delete', 'POST');
|
||||
$router->write('/$id/ban', 'account', 'ban', 'POST');
|
||||
$router->write('/$id/unban', 'account', 'unban', 'POST');
|
||||
$router->write('/session/worker', 'session', 'worker', 'POST');
|
||||
$router->write('/session/write', 'session', 'write', 'POST');
|
||||
$router->write('/session/read', 'session', 'read', 'POST');
|
||||
@@ -79,6 +83,7 @@ $router->write('/session/invite', 'session', 'invite', 'POST');
|
||||
$router->write('/tasks/create', 'task', 'create', 'POST');
|
||||
$router->write('/tasks/read', 'task', 'read', 'POST');
|
||||
$router->write('/works/list', 'work', 'datalist', 'POST');
|
||||
$router->write('/tasks/works', 'task', 'works', 'POST');
|
||||
$router->write('/task/$task/read', 'task', 'task', 'POST');
|
||||
$router->write('/task/$task/value', 'task', 'value', 'POST');
|
||||
$router->write('/task/$task/confirm', 'task', 'confirm', 'POST');
|
||||
@@ -88,7 +93,6 @@ $router->write('/task/$task/hide', 'task', 'hide', 'POST');
|
||||
$router->write('/task/$task/remove', 'task', 'remove', 'POST');
|
||||
$router->write('/task/$task/work', 'task', 'work', 'POST');
|
||||
$router->write('/task/$task/date', 'task', 'date', 'POST');
|
||||
$router->write('/task/$task/works', 'task', 'works', 'POST');
|
||||
$router->write('/task/$task/description', 'task', 'description', 'POST');
|
||||
$router->write('/task/$task/commentary', 'task', 'commentary', 'POST');
|
||||
$router->write('/task/$task/worker/update', 'task', 'update', 'POST');
|
||||
@@ -98,6 +102,12 @@ $router->write('/task/$task/unpublish', 'task', 'unpublish', 'POST');
|
||||
$router->write('/task/$task/chat', 'task', 'chat', 'POST');
|
||||
$router->write('/task/$task/chat/send', 'task', 'message', 'POST');
|
||||
$router->write('/elements/menu', 'index', 'menu', 'POST');
|
||||
$router->write('/payments/workers', 'payments', 'workers', 'POST');
|
||||
$router->write('/payments/markets', 'payments', 'markets', 'POST');
|
||||
$router->write('/payments/confirm/$type', 'payments', 'confirm', 'POST');
|
||||
$router->write('/settings', 'settings', 'index', 'GET');
|
||||
$router->write('/settings', 'settings', 'index', 'POST');
|
||||
$router->write('/settings/$id/write', 'settings', 'write', 'POST');
|
||||
|
||||
// Инициализация ядра
|
||||
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
|
||||
|
@@ -338,7 +338,7 @@ if (typeof window.administrators !== "function") {
|
||||
|
||||
// Инициализация оболочки всплывающего окна
|
||||
const popup = document.createElement("section");
|
||||
popup.classList.add("list", "small");
|
||||
popup.classList.add("list", "extensive", "small");
|
||||
|
||||
// Инициализация заголовка всплывающего окна
|
||||
const title = document.createElement("h3");
|
||||
@@ -1056,7 +1056,7 @@ if (typeof window.administrators !== "function") {
|
||||
);
|
||||
|
||||
/**
|
||||
* Сгенерировать окно с формой создания аккаунт
|
||||
* Сгенерировать окно с формой создания аккаунта
|
||||
*
|
||||
* @param {HTMLElement} row Строка
|
||||
*
|
||||
@@ -1069,7 +1069,7 @@ if (typeof window.administrators !== "function") {
|
||||
|
||||
// Инициализация оболочки всплывающего окна
|
||||
const popup = document.createElement("section");
|
||||
popup.classList.add("list", "small");
|
||||
popup.classList.add("list", "extensive", "small");
|
||||
|
||||
// Инициализация заголовка всплывающего окна
|
||||
const title = document.createElement("h3");
|
||||
|
@@ -13,7 +13,7 @@ if (typeof window.buffer !== "function") {
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
static write(name, value) {
|
||||
static async write(name, value) {
|
||||
if (
|
||||
typeof core === "function" && typeof name === "string" &&
|
||||
(typeof value === "string" || typeof value === "number")
|
||||
@@ -36,7 +36,7 @@ if (typeof window.buffer !== "function") {
|
||||
});
|
||||
|
||||
// Запрос к серверу для записи в сессию (базу данных)
|
||||
return fetch("/session/write", {
|
||||
return await fetch("/session/write", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
|
@@ -577,7 +577,7 @@ if (typeof window.chat !== "function") {
|
||||
* @param {string} chat Тип чата (market, worker, both)
|
||||
* @param {bool} scroll Прокрутить до последнего сообщения?
|
||||
* @param {bool} sound Проигрывать звук уведомления о новом сообщении?
|
||||
* @param {string} chat Тип чата (market, worker)
|
||||
* @param {bool} force Принудительное выполнение (используется в damper())
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
@@ -601,7 +601,7 @@ if (typeof window.chat !== "function") {
|
||||
* @param {string} chat Тип чата (market, worker, both)
|
||||
* @param {bool} scroll Прокрутить до последнего сообщения?
|
||||
* @param {bool} sound Проигрывать звук уведомления о новом сообщении?
|
||||
* @param {bool} force Принудительное выполнение (используется в damper()
|
||||
* @param {bool} force Принудительное выполнение (используется в damper())
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
|
@@ -23,6 +23,11 @@ if (typeof window.core !== "function") {
|
||||
: (this.subdomain === "xn--80aalqawikqchmc"
|
||||
? "administrator"
|
||||
: "worker")));
|
||||
|
||||
static numbers(e) {
|
||||
const c = (e.which) ? e.which : e.keyCode;
|
||||
return !(c !== 46 && c !== 44 && c > 31 && (c < 48 || c > 57));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -20,13 +20,13 @@ function damper(func, timeout = 300, force) {
|
||||
if (typeof force === 'number' && args[force]) {
|
||||
// Принудительное выполнение (игнорировать таймер)
|
||||
|
||||
func.apply(this, args);
|
||||
return func.apply(this, args);
|
||||
} else {
|
||||
// Обычное выполнение
|
||||
|
||||
// Вызов функции (вход в рекурсию)
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
return func.apply(this, args);
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
|
@@ -3261,3 +3261,7 @@
|
||||
}));
|
||||
//# sourceMappingURL=imask.js.map
|
||||
|
||||
// Вызов события: "инициализировано"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("imask.initialized"),
|
||||
);
|
||||
|
@@ -176,6 +176,13 @@ if (typeof window.loader !== "function") {
|
||||
})
|
||||
.then((response) => response.text())
|
||||
.then((data) => {
|
||||
// Write path in history
|
||||
history.pushState(this.storage, "/settings", "/settings");
|
||||
|
||||
// Write path to the current directory buffer
|
||||
core.page = 'settings';
|
||||
|
||||
// Write content in document
|
||||
document.body.getElementsByTagName("main")[0].innerHTML = data;
|
||||
});
|
||||
}
|
||||
@@ -185,10 +192,7 @@ if (typeof window.loader !== "function") {
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static async account() {
|
||||
// Initialization of the account identifier
|
||||
account = Cookies.get(`account_id`) ?? "account";
|
||||
|
||||
static async account(account = Cookies.get(`account_id`) ?? "account") {
|
||||
return await fetch(`/${account}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -198,10 +202,10 @@ if (typeof window.loader !== "function") {
|
||||
.then((response) => response.text())
|
||||
.then((data) => {
|
||||
// Write path in history
|
||||
history.pushState(this.storage, "/account", "/account");
|
||||
history.pushState(this.storage, `/${account}`, `/${account}`);
|
||||
|
||||
// Write path to the current directory buffer
|
||||
core.page = 'account';
|
||||
core.page = account;
|
||||
|
||||
// Write content in document
|
||||
document.body.getElementsByTagName("main")[0].innerHTML = data;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -111,7 +111,7 @@ if (typeof window.operators !== "function") {
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить администратора (вызов демпфера)
|
||||
* Обновить оператора (вызов демпфера)
|
||||
*
|
||||
* @param {HTMLElement} row Строка <div>
|
||||
* @param {HTMLElement} button Кнопка <button>
|
||||
@@ -122,6 +122,7 @@ if (typeof window.operators !== "function") {
|
||||
* @param {HTMLElement} account_mail Почта аккаунта<input>
|
||||
* @param {HTMLElement} account_password Пароль аккаунта <input>
|
||||
* @param {HTMLElement} account_commentary Комментарий аккаунта <textarea>
|
||||
* @param {HTMLElement} account_transactions Предоставить доступ к транзакциям? <checkbox>
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
@@ -135,6 +136,7 @@ if (typeof window.operators !== "function") {
|
||||
account_mail,
|
||||
account_password,
|
||||
account_commentary,
|
||||
account_transactions
|
||||
) {
|
||||
// Блокировка полей ввода
|
||||
account_name_first.setAttribute("readonly", true);
|
||||
@@ -144,6 +146,7 @@ if (typeof window.operators !== "function") {
|
||||
account_mail.setAttribute("readonly", true);
|
||||
account_password.setAttribute("readonly", true);
|
||||
account_commentary.setAttribute("readonly", true);
|
||||
account_transactions.setAttribute("readonly", true);
|
||||
|
||||
// Блокировка кнопки
|
||||
button.setAttribute("disabled", true);
|
||||
@@ -159,11 +162,12 @@ if (typeof window.operators !== "function") {
|
||||
account_mail,
|
||||
account_password,
|
||||
account_commentary,
|
||||
account_transactions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить администратора (демпфер)
|
||||
* Обновить оператора (демпфер)
|
||||
*
|
||||
* @param {HTMLElement} row Строка <div>
|
||||
* @param {HTMLElement} button Кнопка <button>
|
||||
@@ -174,6 +178,7 @@ if (typeof window.operators !== "function") {
|
||||
* @param {HTMLElement} account_mail Почта аккаунта<input>
|
||||
* @param {HTMLElement} account_password Пароль аккаунта <input>
|
||||
* @param {HTMLElement} account_commentary Комментарий аккаунта <textarea>
|
||||
* @param {HTMLElement} account_transactions Предоставить доступ к транзакциям? <checkbox>
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
@@ -188,6 +193,7 @@ if (typeof window.operators !== "function") {
|
||||
account_mail,
|
||||
account_password,
|
||||
account_commentary,
|
||||
account_transactions
|
||||
) => {
|
||||
// Инициализация функции разблокировки
|
||||
function unblock() {
|
||||
@@ -199,6 +205,7 @@ if (typeof window.operators !== "function") {
|
||||
account_mail.removeAttribute("readonly");
|
||||
account_password.removeAttribute("readonly");
|
||||
account_commentary.removeAttribute("readonly");
|
||||
account_transactions.removeAttribute("readonly");
|
||||
|
||||
// Разблокировка кнопки
|
||||
button.removeAttribute("disabled");
|
||||
@@ -220,7 +227,7 @@ if (typeof window.operators !== "function") {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body:
|
||||
`name_first=${account_name_first.value}&name_second=${account_name_second.value}&name_last=${account_name_last.value}&number=${account_number.mask.unmaskedValue}&mail=${account_mail.value}&password=${account_password.value}&commentary=${account_commentary.value}`,
|
||||
`name_first=${account_name_first.value}&name_second=${account_name_second.value}&name_last=${account_name_last.value}&number=${account_number.mask.unmaskedValue}&mail=${account_mail.value}&password=${account_password.value}&commentary=${account_commentary.value}&transactions=${account_transactions.checked}`,
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
@@ -338,7 +345,7 @@ if (typeof window.operators !== "function") {
|
||||
|
||||
// Инициализация оболочки всплывающего окна
|
||||
const popup = document.createElement("section");
|
||||
popup.classList.add("list", "small");
|
||||
popup.classList.add("list", "extensive", "small");
|
||||
|
||||
// Инициализация заголовка всплывающего окна
|
||||
const title = document.createElement("h3");
|
||||
@@ -574,6 +581,34 @@ if (typeof window.operators !== "function") {
|
||||
);
|
||||
account_commentary_textarea.value = data.commentary ?? "";
|
||||
|
||||
// Инициализация строки
|
||||
const account_transactions = document.createElement("div");
|
||||
account_commentary.classList.add("row");
|
||||
|
||||
// Инициализация оболочки для строки
|
||||
const account_transactions_label = document.createElement("label");
|
||||
account_commentary_label.setAttribute("id", "account_transactions");
|
||||
|
||||
// Инициализация заголовка для поля ввода
|
||||
const account_transactions_title = document.createElement("b");
|
||||
account_transactions_title.classList.add(
|
||||
"separated",
|
||||
"right",
|
||||
"unselectable",
|
||||
);
|
||||
account_transactions_title.innerText = "Транзакции:";
|
||||
|
||||
// Инициализация поля ввода
|
||||
const account_transactions_input = document.createElement(
|
||||
"input",
|
||||
);
|
||||
account_transactions_input.setAttribute('type', 'checkbox');
|
||||
account_transactions_input.setAttribute(
|
||||
"title",
|
||||
"Предоставить аккаунту доступ к выплатам?",
|
||||
);
|
||||
if (data.transactions) account_transactions_input.setAttribute('checked', 'true');
|
||||
|
||||
// Инициализация строки
|
||||
const account_buttons = document.createElement("div");
|
||||
account_buttons.classList.add("row", "divided", "buttons");
|
||||
@@ -667,6 +702,11 @@ if (typeof window.operators !== "function") {
|
||||
account_commentary.appendChild(account_commentary_label);
|
||||
account.appendChild(account_commentary);
|
||||
|
||||
account_transactions_label.appendChild(account_transactions_title);
|
||||
account_transactions_label.appendChild(account_transactions_input);
|
||||
account_transactions.appendChild(account_transactions_label);
|
||||
account.appendChild(account_transactions);
|
||||
|
||||
account_buttons.appendChild(account_update_button);
|
||||
account_buttons.appendChild(account_delete_button);
|
||||
account.appendChild(account_buttons);
|
||||
@@ -1069,7 +1109,7 @@ if (typeof window.operators !== "function") {
|
||||
|
||||
// Инициализация оболочки всплывающего окна
|
||||
const popup = document.createElement("section");
|
||||
popup.classList.add("list", "small");
|
||||
popup.classList.add("list", "extensive", "small");
|
||||
|
||||
// Инициализация заголовка всплывающего окна
|
||||
const title = document.createElement("h3");
|
||||
|
321
mirzaev/ebala/system/public/js/payments.js
Normal file
321
mirzaev/ebala/system/public/js/payments.js
Normal file
@@ -0,0 +1,321 @@
|
||||
"use strict";
|
||||
|
||||
if (typeof window.payments !== "function") {
|
||||
// Not initialized
|
||||
|
||||
// Initialize of the class in global namespace
|
||||
window.payments = class payments {
|
||||
/**
|
||||
* Сотрудники
|
||||
*
|
||||
* Сгенерировать и скачать excel-документ с зарплатами сотрудников за выбранный период (cookies или session storage)
|
||||
*
|
||||
* @return {void} Вызывает функцию скачивания в браузере
|
||||
*/
|
||||
static workers = damper(() => {
|
||||
// Инициализация оболочки фильтров
|
||||
const filters = document.getElementById("filters").children[0];
|
||||
|
||||
tasks
|
||||
.filter("from", new Date(filters.children[0].value) / 1000, null, true)
|
||||
.then(() => {
|
||||
tasks
|
||||
.filter(
|
||||
"to",
|
||||
new Date(filters.children[1].value) / 1000,
|
||||
null,
|
||||
true,
|
||||
)
|
||||
.then(() => {
|
||||
// Запрос к серверу
|
||||
fetch("/payments/workers", { method: "POST" }).then(
|
||||
(response) => {
|
||||
if (response.ok) {
|
||||
// Сервер вернул код успешного выполнения
|
||||
|
||||
response
|
||||
.clone()
|
||||
.json()
|
||||
.then(
|
||||
(data) => {
|
||||
if (this.errors(data.errors)) {
|
||||
// Сгенерированы ошибки
|
||||
} else {
|
||||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||||
}
|
||||
},
|
||||
() => {
|
||||
// Инициализация имени файла
|
||||
const header = response.headers.get(
|
||||
"Content-Disposition",
|
||||
);
|
||||
|
||||
if (header !== null) {
|
||||
// Найден заголовок (подразумевается, что передан файл, а не случайная ошибка)
|
||||
|
||||
// Инициализация названия файла
|
||||
const name = header.split(";")[1].split("=")[1];
|
||||
|
||||
// Чтение полученного файла (подразумевается ошибка при инициализации json)
|
||||
response.blob().then((blob) => {
|
||||
// Инициализация временного элемента для скачивания с именем файла (хак)
|
||||
const element = document.createElement("a");
|
||||
element.href = window.URL.createObjectURL(blob);
|
||||
element.download = name;
|
||||
element.style.setProperty("display", "none");
|
||||
document.body.appendChild(element);
|
||||
|
||||
// Скачивание файла
|
||||
element.click();
|
||||
|
||||
// Деинициализация временного элемента
|
||||
element.remove();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}, 200);
|
||||
|
||||
/**
|
||||
* Магазины
|
||||
*
|
||||
* Сгенерировать и скачать excel-документ со ... (сверкой?) за выбранный период (cookies или session storage)
|
||||
*
|
||||
* @return {void} Вызывает функцию скачивания в браузере
|
||||
*/
|
||||
static markets = damper(() => {
|
||||
// Инициализация оболочки фильтров
|
||||
const filters = document.getElementById("filters").children[0];
|
||||
|
||||
tasks
|
||||
.filter("from", new Date(filters.children[0].value) / 1000, null, true)
|
||||
.then(() => {
|
||||
tasks
|
||||
.filter(
|
||||
"to",
|
||||
new Date(filters.children[1].value) / 1000,
|
||||
null,
|
||||
true,
|
||||
)
|
||||
.then(() => {
|
||||
// Запрос к серверу
|
||||
fetch("/payments/markets", { method: "POST" }).then(
|
||||
(response) => {
|
||||
if (response.ok) {
|
||||
// Сервер вернул код успешного выполнения
|
||||
|
||||
response
|
||||
.clone()
|
||||
.json()
|
||||
.then(
|
||||
(data) => {
|
||||
if (this.errors(data.errors)) {
|
||||
// Сгенерированы ошибки
|
||||
} else {
|
||||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||||
}
|
||||
},
|
||||
() => {
|
||||
// Инициализация имени файла
|
||||
const header = response.headers.get(
|
||||
"Content-Disposition",
|
||||
);
|
||||
|
||||
if (header !== null) {
|
||||
// Найден заголовок (подразумевается, что передан файл, а не случайная ошибка)
|
||||
|
||||
// Инициализация названия файла
|
||||
const name = header.split(";")[1].split("=")[1];
|
||||
|
||||
// Чтение полученного файла (подразумевается ошибка при инициализации json)
|
||||
response.blob().then((blob) => {
|
||||
// Инициализация временного элемента для скачивания с именем файла (хак)
|
||||
const element = document.createElement("a");
|
||||
element.href = window.URL.createObjectURL(blob);
|
||||
element.download = name;
|
||||
element.style.setProperty("display", "none");
|
||||
document.body.appendChild(element);
|
||||
|
||||
// Скачивание файла
|
||||
element.click();
|
||||
|
||||
// Деинициализация временного элемента
|
||||
element.remove();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}, 200);
|
||||
|
||||
/**
|
||||
* Подтвердить обработку документа
|
||||
*
|
||||
* @param {string} type Тип обработанного документа (workers, markets)
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static confirm = damper((type) => {
|
||||
// Инициализация оболочки фильтров
|
||||
const filters = document.getElementById("filters").children[0];
|
||||
|
||||
tasks
|
||||
.filter("from", new Date(filters.children[0].value) / 1000, null, true)
|
||||
.then(() => {
|
||||
tasks
|
||||
.filter(
|
||||
"to",
|
||||
new Date(filters.children[1].value) / 1000,
|
||||
null,
|
||||
true,
|
||||
)
|
||||
.then(() => {
|
||||
// Запрос к серверу
|
||||
fetch(`/payments/confirm/${type}`, { method: "POST" }).then(
|
||||
(response) => {
|
||||
if (response.ok) {
|
||||
// Сервер вернул код успешного выполнения
|
||||
|
||||
response
|
||||
.clone()
|
||||
.json()
|
||||
.then(
|
||||
(data) => {
|
||||
if (this.errors(data.errors)) {
|
||||
// Сгенерированы ошибки
|
||||
} else {
|
||||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}, 200);
|
||||
|
||||
/**
|
||||
* Сгенерировать HTML-элемент со списком ошибок
|
||||
*
|
||||
* @param {object} registry Реестр ошибок
|
||||
* @param {bool} render Отобразить в окне с ошибками?
|
||||
* @param {bool} clean Очистить окно с ошибками перед добавлением?
|
||||
*
|
||||
* @return {bool} Сгенерированы ошибки?
|
||||
*
|
||||
* @TODO Переделать под показ ошибок где-нибудь
|
||||
*/
|
||||
static errors(registry, render = true, clean = true) {
|
||||
// Инициализация ссылки на HTML-элемент с ошибками
|
||||
const wrap = document.body.contains(this.body.errors)
|
||||
? this.body.errors
|
||||
: document.querySelector('[data-errors="true"]');
|
||||
|
||||
if (wrap instanceof HTMLElement && document.body.contains(wrap)) {
|
||||
// Найден HTML-элемент с ошибками
|
||||
|
||||
// Перерасчёт высоты элемента
|
||||
function height() {
|
||||
wrap.classList.remove("hidden");
|
||||
wrap.classList.remove("animation");
|
||||
// Реинициализация переменной с данными о высоте HTML-элемента (16 - это padding-top + padding-bottom у div#popup > section.errors)
|
||||
wrap.style.setProperty("--height", wrap.offsetHeight - 16 + "px");
|
||||
wrap.classList.add("animation");
|
||||
wrap.classList.add("hidden");
|
||||
}
|
||||
|
||||
// Инициализация элемента-списка ошибок
|
||||
const list = wrap.getElementsByTagName("dl")[0];
|
||||
|
||||
// Удаление ошибок из прошлой генерации
|
||||
if (clean) list.innerHTML = null;
|
||||
|
||||
for (const error in registry) {
|
||||
// Генерация HTML-элементов с текстами ошибок
|
||||
|
||||
// Инициализация HTML-элемента текста ошибки
|
||||
const samp = document.createElement("samp");
|
||||
|
||||
if (typeof registry[error] === "object") {
|
||||
// Категория ошибок
|
||||
|
||||
// Проверка наличия ошибок
|
||||
if (registry[error].length === 0) continue;
|
||||
|
||||
// Инициализация HTML-элемента-оболочки
|
||||
const wrap = document.createElement("dt");
|
||||
|
||||
// Запись текста категории
|
||||
samp.innerText = error;
|
||||
|
||||
// Запись HTML-элементов в список
|
||||
wrap.appendChild(samp);
|
||||
list.appendChild(wrap);
|
||||
|
||||
// Реинициализация высоты
|
||||
height();
|
||||
|
||||
// Обработка вложенных ошибок (вход в рекурсию)
|
||||
this.errors(registry[error], false);
|
||||
} else {
|
||||
// Текст ошибки (подразумевается)
|
||||
|
||||
// Инициализация HTML-элемента
|
||||
const wrap = document.createElement("dd");
|
||||
|
||||
// Запись текста ошибки
|
||||
samp.innerText = registry[error];
|
||||
|
||||
// Запись HTML-элемента в список
|
||||
wrap.appendChild(samp);
|
||||
list.appendChild(wrap);
|
||||
|
||||
// Реинициализация высоты
|
||||
height();
|
||||
}
|
||||
}
|
||||
|
||||
if (render) {
|
||||
// Запрошена отрисовка
|
||||
|
||||
if (list.childElementCount !== 0) {
|
||||
// Найдены ошибки
|
||||
|
||||
// Сброс анимации
|
||||
// УЛОВКА: таким образом не запускается анимация до взаимодействия с элементом (исправлял это в CSS, но не помню как)
|
||||
wrap.classList.add("animation");
|
||||
|
||||
// Отображение
|
||||
wrap.classList.remove("hidden");
|
||||
} else {
|
||||
// Не найдены ошибки
|
||||
|
||||
// Скрытие
|
||||
wrap.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
return list.childElementCount === 0 ? false : true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Вызов события: "инициализировано"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("payments.initialized", {
|
||||
detail: { payments: window.payments },
|
||||
}),
|
||||
);
|
42
mirzaev/ebala/system/public/js/settings.js
Normal file
42
mirzaev/ebala/system/public/js/settings.js
Normal file
@@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
|
||||
if (typeof window.settings !== "function") {
|
||||
// Not initialized
|
||||
|
||||
// Initialize of the class in global namespace
|
||||
window.settings = class settings {
|
||||
/**
|
||||
* Записать (дампер)
|
||||
*
|
||||
* Отправляет запрос на сервер для записи в ArangoDB
|
||||
*
|
||||
* @param {string|number} id Идентификатор (_key)
|
||||
* @param {string} value Значение
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static write = damper((id, value) => {
|
||||
if (
|
||||
(typeof id === "string" || typeof id === "number") && typeof value === "string" && value.length > 0
|
||||
) {
|
||||
// Получены все обязательные аргументы
|
||||
|
||||
// Запрос к серверу
|
||||
fetch(`/settings/${id}/write`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: `value=${value}`,
|
||||
});
|
||||
}
|
||||
}, 400);
|
||||
};
|
||||
}
|
||||
|
||||
// Вызов события: "инициализировано"
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("settings.initialized", {
|
||||
detail: { settings: window.settings },
|
||||
}),
|
||||
);
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<section class="row merged chat cloud rounded" data-chat-element="messages">
|
||||
{% for message in messages %}
|
||||
<div class="message {% if message.type != 'message' %}{{ message.type }}{% endif %}">
|
||||
<h3 class="coal"><b>{{ message.from._key }}</b> <span class="unselectable">{{ message.from.type|account_type_to_russian }}</span> <span class="date unselectable"><b>{{ message.date|date('H:i d.m.Y') }}</b></span></h3>
|
||||
<h3 class="coal"><b>{{ message.from.id ?? message.from._key }}</b> <span class="unselectable">{{ message.from.type|account_type_to_russian }}</span> <span class="date unselectable"><b>{{ message.date|date('H:i d.m.Y') }}</b></span></h3>
|
||||
<p>{{ message.text }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<!-- MARKET #{{ market.id.value }} -->
|
||||
{% for key, data in market | filter((data, key) => key != '_key') -%}
|
||||
{% for key, data in market | filter((data, key) => key != 'id' and key != '_key') -%}
|
||||
{% if key == 'created' or key == 'updated' %}
|
||||
<span id="{{ market.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ data.value is empty ? 'Никогда' :
|
||||
data.value|date('Y.m.d H:i:s') }}</span>
|
||||
@@ -7,10 +7,10 @@
|
||||
<span id="{{ market.id.value }}_number"><b>{{ data.label }}:</b><a href="tel:{{ data.value }}" title="Позвонить">{{
|
||||
data.value }}</a></span>
|
||||
{% elseif key == 'mail' %}
|
||||
<span id="{{ worker.id.value }}_number"><b>{{ data.label }}:</b><a href="mailto:{{ data.value }}" title="Написать">{{
|
||||
<span id="{{ market.id.value }}_number"><b>{{ data.label }}:</b><a href="mailto:{{ data.value }}" title="Написать">{{
|
||||
data.value }}</a></span>
|
||||
{% elseif key == 'name' %}
|
||||
<span id="{{ worker._key.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value.first is not empty %}{{ data.value.first }}{% endif %}{% if data.value.second is not empty %} {{ data.value.second }}{% endif %}{% if data.value.last is not empty %} {{ data.value.last }}{% endif %}</span>
|
||||
<span id="{{ market.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value.first is not empty %}{{ data.value.first }}{% endif %}{% if data.value.second is not empty %} {{ data.value.second }}{% endif %}{% if data.value.last is not empty %} {{ data.value.last }}{% endif %}</span>
|
||||
{% else %}
|
||||
<span id="{{ market.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value is same as(true) %}Да{% elseif data.value is same as(false) %}Нет{% elseif data.value is empty %}{% else %}{{ data.value }}{% endif %}</span>
|
||||
{% endif %}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<!-- TASK #{{ task._key.value }} -->
|
||||
{% for key, data in task | filter((data, key) => key != '_key') -%}
|
||||
{% for key, data in task | filter((data, key) => key != '_key' and key != 'updates') -%}
|
||||
{% if (key == 'created' or key == 'updated') %}
|
||||
<span id="{{ task.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ data.value is empty ? 'Никогда' :
|
||||
data.value|date('d.m.Y H:i') }}</span>
|
||||
@@ -11,3 +11,13 @@
|
||||
data.value is same as(false) %}Нет{% elseif data.value is empty %}{% else %}{{ data.value }}{% endif %}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if account.type == 'administrator' or account.type == 'operator' %}
|
||||
<h4 class="separator unselectable">Последние изменения</h4>
|
||||
{% for key, data in task.updates %}
|
||||
<span id="{{ task._key }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value.id is empty %}{% else %}{%
|
||||
if data.value.first is not empty %}{{
|
||||
data.value.first|slice(0, 1)|upper }}.{% endif %}{% if data.value.last is not empty %} {{
|
||||
data.value.last|slice(0, 1)|upper }}.{% endif %}{% if data.value.second is not empty %} {{
|
||||
data.value.second }} ({{ data.value.id }}){% else %}{{ data.value.id }}{% endif %}{% endif %}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<!-- TASK #{{ task._key.value }} -->
|
||||
{% for key, data in task | filter((data, key) => key != '_key') -%}
|
||||
{% for key, data in task | filter((data, key) => key != '_key' and key != 'updates') -%}
|
||||
{% if (key == 'created' or key == 'updated' or key == 'start' or key == 'end') %}
|
||||
<span id="{{ task.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ data.value is empty ? 'Никогда' :
|
||||
data.value|date('d.m.Y H:i', ) }}</span>
|
||||
data.value|date('d.m.Y H:i') }}</span>
|
||||
{% elseif key == 'confirmed' or key == 'hided' %}
|
||||
<span id="{{ task.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value is same as(true) %}Да{% elseif
|
||||
data.value is same as(false) or data.value is empty %}Нет{% else %}{{ data.value }}{% endif %}</span>
|
||||
@@ -11,3 +11,13 @@
|
||||
data.value is same as(false) %}Нет{% elseif data.value is empty %}{% else %}{{ data.value }}{% endif %}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if account.type == 'administrator' or account.type == 'operator' %}
|
||||
<h4 class="separator unselectable">Последние изменения</h4>
|
||||
{% for key, data in task.updates %}
|
||||
<span id="{{ task._key }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value.id is empty %}{% else %}{%
|
||||
if data.value.first is not empty %}{{
|
||||
data.value.first|slice(0, 1)|upper }}.{% endif %}{% if data.value.last is not empty %} {{
|
||||
data.value.last|slice(0, 1)|upper }}.{% endif %}{% if data.value.second is not empty %} {{
|
||||
data.value.second }} ({{ data.value.id }}){% else %}{{ data.value.id }}{% endif %}{% endif %}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
@@ -1,21 +1,21 @@
|
||||
<!-- WORKER #{{ worker.id.value }} -->
|
||||
{% for key, data in worker | filter((data, key) => key != '_key') -%}
|
||||
{% for key, data in worker | filter((data, key) => key != 'id' and key != '_key') -%}
|
||||
{% if key == 'created' or key == 'updated' %}
|
||||
<span id="{{ worker._key.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ data.value is empty ? 'Никогда' :
|
||||
<span id="{{ worker.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ data.value is empty ? 'Никогда' :
|
||||
data.value|date('Y.m.d H:i:s') }}</span>
|
||||
{% elseif key == 'hiring' or key == 'birth' or key == 'issued' %}
|
||||
<span id="{{ worker._key.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ data.value|date('Y.m.d') }}</span>
|
||||
<span id="{{ worker.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ data.value|date('Y.m.d') }}</span>
|
||||
{% elseif key == 'number' %}
|
||||
<span id="{{ worker._key.value }}_number"><b>{{ data.label }}:</b><a href="tel:{{ data.value }}" title="Позвонить">{{
|
||||
<span id="{{ worker.id.value }}_number"><b>{{ data.label }}:</b><a href="tel:{{ data.value }}" title="Позвонить">{{
|
||||
data.value }}</a></span>
|
||||
{% elseif key == 'mail' %}
|
||||
<span id="{{ worker.id.value }}_number"><b>{{ data.label }}:</b><a href="mailto:{{ data.value }}" title="Написать">{{
|
||||
data.value }}</a></span>
|
||||
{% elseif key == 'name' %}
|
||||
<span id="{{ worker._key.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value.first is not empty %}{{ data.value.first }}{% endif %}{% if data.value.second is not empty %} {{ data.value.second }}{% endif %}{% if data.value.last is not empty %} {{ data.value.last }}{% endif %}</span>
|
||||
<span id="{{ worker.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value.first is not empty %}{{ data.value.first }}{% endif %}{% if data.value.second is not empty %} {{ data.value.second }}{% endif %}{% if data.value.last is not empty %} {{ data.value.last }}{% endif %}</span>
|
||||
{% elseif key == 'department' %}
|
||||
<span id="{{ worker._key.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ (data.value.number ~ ' ' ~ data.value.address)|trim }}</span>
|
||||
<span id="{{ worker.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{{ (data.value.number ~ ' ' ~ data.value.address)|trim }}</span>
|
||||
{% else %}
|
||||
<span id="{{ worker._key.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value is same as(true) %}Да{% elseif data.value is same as(false) %}Нет{% elseif data.value is empty %}{% else %}{{ data.value }}{% endif %}</span>
|
||||
<span id="{{ worker.id.value }}_{{ key }}"><b>{{ data.label }}:</b>{% if data.value is same as(true) %}Да{% elseif data.value is same as(false) %}Нет{% elseif data.value is empty %}{% else %}{{ data.value }}{% endif %}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
{% for row in rows %}
|
||||
<div id="{{ row.account._key }}" class="row{% if row.account.active is same as(true) %} active{% else %} hided{% endif %}" data-row="market">
|
||||
<span class="unselectable interactive" data-column="account" title="Аккаунт" onclick="markets.account.update(this.parentElement)">{{ row.account._key }}</span>
|
||||
<span class="unselectable interactive" data-column="market" title="Магазин" onclick="markets.update(this.parentElement)">{{ row.market._key }}</span>
|
||||
<span class="unselectable interactive" data-column="market" title="Магазин" onclick="markets.update(this.parentElement)">{{ row.market.id }}</span>
|
||||
<span class="unselectable interactive" data-column="name" title="{% if row.market.name.first is not empty %}{{ row.market.name.first }}{% endif %}{% if row.market.name.second is not empty %} {{ row.market.name.second }}{% endif %}{% if row.market.name.last is not empty %} {{ row.market.name.last }}{% endif %}">{% if row.market.name.first is not empty %}{{ row.market.name.first|slice(0, 1)|upper }}.{% endif %}{% if row.market.name.last is not empty %} {{ row.market.name.last|slice(0, 1)|upper }}.{% endif %}{% if row.market.name.second is not empty %} {{ row.market.name.second }}{% endif %}</span>
|
||||
<span class="unselectable interactive" data-column="number"><a href="tel:{{ row.market.number }}" title="Позвонить">{{ row.market.number|storaged_number_to_readable }}</a></span>
|
||||
<span class="unselectable interactive" data-column="mail"><a href="mailto:{{ row.market.mail }}" title="Написать письмо">{{ row.market.mail }}</a></span>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{% if page != null %}<!-- PAGE #{{ page }} -->{% endif %}
|
||||
{% for row in rows %}
|
||||
<div id="{{ row.account._key }}"
|
||||
class="row{% if row.account.active is same as(true) %} active{% else %} hided{% endif %}" data-row="operator">
|
||||
class="row{% if row.account.active is same as(true) %} active{% else %} hided{% endif %}{% if row.account.transactions is same as(true) %} transactions{% endif %}" data-row="operator">
|
||||
<span class="unselectable interactive" data-column="account" title="{{ row.account._key }}"
|
||||
onclick="operators.update(this.parentElement)">{{
|
||||
row.account._key }}</span>
|
||||
|
@@ -10,14 +10,14 @@
|
||||
}}</span>
|
||||
<span class="unselectable interactive" data-column="worker" title="{{ row.worker.id }}" {% if account.type !='worker'
|
||||
%}onclick="tasks.worker.popup(this.parentElement)" {% endif %}>{{
|
||||
row.worker._key }}</span>
|
||||
row.worker.id }}</span>
|
||||
<span class="unselectable interactive" data-column="name"
|
||||
title="{% if row.worker.name.first is not empty %}{{ row.worker.name.first }}{% endif %}{% if row.worker.name.second is not empty %} {{ row.worker.name.second }}{% endif %}{% if row.worker.name.last is not empty %} {{ row.worker.name.last }}{% endif %}">{%
|
||||
if row.worker.name.first is not empty %}{{
|
||||
row.worker.name.first|slice(0, 1)|upper }}.{% endif %}{% if row.worker.name.last is not empty %} {{
|
||||
row.worker.name.last|slice(0, 1)|upper }}.{% endif %}{% if row.worker.name.second is not empty %} {{
|
||||
row.worker.name.second }}{% endif %}</span>
|
||||
<span class="unselectable interactive" data-column="task" title="{{ row.task.description }}">{{ row.task.work
|
||||
<span class="unselectable interactive" data-column="work" title="{{ row.task.description }}">{{ row.task.work|work
|
||||
}}</span>
|
||||
<span class="unselectable interactive" data-column="start">{{
|
||||
row.task.generated.start }}</span>
|
||||
@@ -27,7 +27,7 @@
|
||||
row.task.generated.hours }}</span>
|
||||
<span class="unselectable interactive" data-column="market" {% if account.type !='market' and account.type !='worker'
|
||||
%}onclick="tasks.market.popup(this.parentElement)" {% endif %}>{{
|
||||
row.market._key }}</span>
|
||||
row.market.id }}</span>
|
||||
<span class="unselectable interactive" data-column="address"
|
||||
title="{% if row.market.city is not null %}{{ row.market.city }}, {% endif %}{{ row.market.address }}">{% if
|
||||
row.market.city is not null %}{{ row.market.city }}, {% endif
|
||||
|
@@ -1,38 +1,46 @@
|
||||
{% if page != null %}<!-- PAGE #{{ page }} -->{% endif %}
|
||||
{% for row in rows %}
|
||||
<div id="{{ row.account._key }}"
|
||||
class="row{% if row.account.active is same as(true) %} active{% else %} hided{% endif %}" data-row="worker">
|
||||
<span class="unselectable interactive" data-column="account" title="Настройки аккаунта" onclick="workers.account.update(this.parentElement)">{{
|
||||
class="row{% if row.account.active is same as(true) %} active{% endif %}{% if row.account.banned is same as(true) %} banned{% endif %}{% if row.worker.fired is same as(true) %} fired{% endif %}" data-row="worker">
|
||||
<span class="unselectable interactive" data-column="account" title="Настройки аккаунта"
|
||||
onclick="workers.account.update(this.parentElement)">{{
|
||||
row.account._key }}</span>
|
||||
<span class="unselectable interactive" data-column="worker" title="Настройки сотрудника" onclick="workers.update(this.parentElement)">{{
|
||||
row.worker._key }}</span>
|
||||
<span class="unselectable interactive" data-column="worker" title="Настройки сотрудника"
|
||||
onclick="workers.update(this.parentElement)">{{
|
||||
row.worker.id }}</span>
|
||||
<span class="unselectable interactive" data-column="name"
|
||||
title="{% if row.worker.name.first is not empty %}{{ row.worker.name.first }}{% endif %}{% if row.worker.name.second is not empty %} {{ row.worker.name.second }}{% endif %}{% if row.worker.name.last is not empty %} {{ row.worker.name.last }}{% endif %}{% if row.worker.birth is not empty %} {{ row.worker.birth|date('d.m.Y') }}{% endif %}" onclick="navigator.clipboard.writeText('{% if row.worker.name.first is not empty %}{{ row.worker.name.first }}{% endif %}{% if row.worker.name.second is not empty %} {{ row.worker.name.second }}{% endif %}{% if row.worker.name.last is not empty %} {{ row.worker.name.last }}{% endif %}{% if row.worker.birth is not empty %} {{ row.worker.birth|date('d.m.Y') }}{% endif %}')">{%
|
||||
title="{% if row.worker.name.first is not empty %}{{ row.worker.name.first }}{% endif %}{% if row.worker.name.second is not empty %} {{ row.worker.name.second }}{% endif %}{% if row.worker.name.last is not empty %} {{ row.worker.name.last }}{% endif %}{% if row.worker.birth is not empty %} {{ row.worker.birth|date('d.m.Y') }}{% endif %}"
|
||||
onclick="navigator.clipboard.writeText('{% if row.worker.name.first is not empty %}{{ row.worker.name.first }}{% endif %}{% if row.worker.name.second is not empty %} {{ row.worker.name.second }}{% endif %}{% if row.worker.name.last is not empty %} {{ row.worker.name.last }}{% endif %}{% if row.worker.birth is not empty %} {{ row.worker.birth|date('d.m.Y') }}{% endif %}')">{%
|
||||
if row.worker.name.first is not empty %}{{
|
||||
row.worker.name.first|slice(0, 1)|upper }}.{% endif %}{% if row.worker.name.last is not empty %} {{
|
||||
row.worker.name.last|slice(0, 1)|upper }}.{% endif %}{% if row.worker.name.second is not empty %} {{
|
||||
row.worker.name.second }}{% endif %}</span>
|
||||
<span class="unselectable interactive" data-column="number"><a href="tel:{{ row.worker.number }}"
|
||||
title="Позвонить">{{ row.worker.number|storaged_number_to_readable }}</a></span>
|
||||
<span class="unselectable interactive" data-column="mail"><a href="mailto:{{ row.worker.mail }}"
|
||||
title="Написать">{{ row.worker.mail }}</a></span>
|
||||
<span class="unselectable interactive" data-column="number"><a href="tel:{{ row.worker.number }}" title="Позвонить">{{
|
||||
row.worker.number|storaged_number_to_readable }}</a></span>
|
||||
<span class="unselectable interactive" data-column="work" title="{% for work in row.worker.works %}{{ work }}{% if not loop.last %}, {% endif %}{% endfor %}">{% for work in row.worker.works %}{{ work|work }}{% if not loop.last %}, {% endif %}{% endfor %}</span>
|
||||
<span class="unselectable interactive" data-column="address"
|
||||
title="{{ (row.worker.city ~ ' ' ~ row.worker.district ~ ' ' ~ row.worker.address)|trim }}"
|
||||
onclick="navigator.clipboard.writeText('{{ row.worker.city ~ ' ' ~ row.worker.district ~ ' ' ~ row.worker.address }}')">{% if row.worker.city is not empty and row.worker.district is not
|
||||
onclick="navigator.clipboard.writeText('{{ row.worker.city ~ ' ' ~ row.worker.district ~ ' ' ~ row.worker.address }}')">{%
|
||||
if row.worker.city is not empty and row.worker.district is not
|
||||
empty and row.worker.district is not null %}{{ row.worker.city|slice(0,4) ~ '. ' ~ row.worker.district|slice(0,3) ~
|
||||
'. ' ~ row.worker.address }}{% else %}{{ row.worker.city ~ ' ' ~ row.worker.district ~ ' ' ~ row.worker.address }}{%
|
||||
endif %}</span>
|
||||
<span class="unselectable interactive" data-column="passport"
|
||||
title="{{ (row.worker.passport ~ ', ' ~ row.worker.issued|date('d.m.Y') ~ ', ' ~ row.worker.department.number ~ ', ' ~ row.worker.department.address)|trim(', ') }}"
|
||||
onclick="navigator.clipboard.writeText('{{ (row.worker.passport ~ ', ' ~ row.worker.issued|date('d.m.Y') ~ ', ' ~ row.worker.department.number ~ ', ' ~ row.worker.department.address)|trim(', ') }}')">{{ (row.worker.passport ~ ', ' ~ row.worker.issued|date('d.m.Y') ~ ', ' ~ row.worker.department.number ~ ', ' ~
|
||||
onclick="navigator.clipboard.writeText('{{ (row.worker.passport ~ ', ' ~ row.worker.issued|date('d.m.Y') ~ ', ' ~ row.worker.department.number ~ ', ' ~ row.worker.department.address)|trim(', ') }}')">{{
|
||||
(row.worker.passport ~ ', ' ~ row.worker.issued|date('d.m.Y') ~ ', ' ~ row.worker.department.number ~ ', ' ~
|
||||
row.worker.department.address)|trim(', ') }}</span>
|
||||
<span class="unselectable interactive" data-column="tax" onclick="navigator.clipboard.writeText('{{ row.worker.tax }}')">{{ row.worker.tax }}</span>
|
||||
<span class="unselectable interactive" data-column="tax"
|
||||
onclick="navigator.clipboard.writeText('{{ row.worker.tax }}')">{{ row.worker.tax }}</span>
|
||||
<span class="unselectable interactive" data-column="requisites"
|
||||
title="{% if row.worker.requisites is not empty and row.worker.payment is not empty %}{{ row.worker.requisites }} ({{ row.worker.payment }}){% else %}{{ row.worker.payment }}{% endif %}"
|
||||
onclick="navigator.clipboard.writeText('{{ row.worker.requisites|storaged_requisites_to_card }}')">{% if
|
||||
row.worker.requisites is not empty and row.worker.payment is not empty %}{{
|
||||
row.worker.requisites|storaged_requisites_preview }} ({{
|
||||
row.worker.payment }}){% else %}{{ row.worker.payment }}{% endif %}</span>
|
||||
title="{% if row.worker.requisites is not empty and row.worker.payment is not empty %}{{ row.worker.requisites }} ({{ row.worker.payment }}){% else %}{{ row.worker.requisites }} {{ row.worker.payment }}{% endif %}"
|
||||
onclick="navigator.clipboard.writeText('{{ row.worker.requisites|storaged_requisites_to_card }}')">
|
||||
{% if row.worker.requisites is not empty and row.worker.payment is not empty %}
|
||||
{{ row.worker.requisites|storaged_requisites_preview }} ({{ row.worker.payment }})
|
||||
{% else %}
|
||||
{{ row.worker.requisites }} {{ row.worker.payment }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="unselectable interactive" data-column="commentary" title="{{ row.account.commentary }}"
|
||||
onclick="navigator.clipboard.writeText('{{ row.account.commentary }}')">{{ row.account.commentary }}</span>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{% for market in markets %}
|
||||
<option value="{{ market.getKey() }}">{{ market.getKey() }}{% if market.name.first is not empty %} {{
|
||||
<option value="{{ market.id }}">{{ market.id }}{% if market.name.first is not empty %} {{
|
||||
market.name.first|slice(0, 1)|upper }}.{% endif %}{% if market.name.last is not empty %} {{ market.name.last|slice(0,
|
||||
1)|upper }}.{% endif %}{% if market.name.second is not empty %} {{ market.name.second }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{% for worker in workers %}
|
||||
<option value="{{ worker.getKey() }}">{{ worker.getKey() }}{% if worker.name.first is not empty %} {{
|
||||
<option value="{{ worker.id }}">{{ worker.id }}{% if worker.name.first is not empty %} {{
|
||||
worker.name.first|slice(0, 1)|upper }}.{% endif %}{% if worker.name.last is not empty %} {{
|
||||
worker.name.last|slice(0, 1)|upper }}.{% endif %}{% if worker.name.second is not empty %} {{
|
||||
worker.name.second }}{% endif %}</option>
|
||||
|
@@ -1,16 +1,36 @@
|
||||
{% if task %}
|
||||
|
||||
{% if exist is same as(true) %}
|
||||
|
||||
{% for work in works %}
|
||||
<option value="{{ work }}" {% if task.work==work %} selected{% endif %}>{{ work }}</option>
|
||||
<option value="{{ work }}" {% if work in worker.works %} selected{% endif %}>b{{ work }}</option>
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% if task is not null %}
|
||||
<optgroup label="Текущее">
|
||||
<option value="{{ task.work }}" selected>{{ task.work }}</option>
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
|
||||
<optgroup label="Доступное">
|
||||
{% for work in works %}
|
||||
<option value="{{ work }}">{{ work }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
|
||||
{% elseif worker %}
|
||||
|
||||
{% for work in works %}
|
||||
<option value="{{ work }}" {% if work in worker.works %} selected{% endif %}>{{ work }}</option>
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% for work in works %}
|
||||
<option value="{{ work }}">{{ work }}</option>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
@@ -26,13 +26,16 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if account.type == 'administrator' %}
|
||||
<!-- <li class="divided">
|
||||
<li class="divided">
|
||||
<button class="transparent" onclick="loader.settings()" title="Глобальные настройки сайта">Настройки</button>
|
||||
</li> -->
|
||||
</li>
|
||||
{% endif %}
|
||||
<li id="account">
|
||||
<button class="transparent" onclick="loader.profile()" title="Настройки профиля">{{ account.name.first }} {{
|
||||
account.name.second }}</button>
|
||||
<button class="transparent" onclick="loader.account({{ account.getKey() }})">{%
|
||||
if account.name.first is not empty %}{{
|
||||
account.name.first|slice(0, 1)|upper }}.{% endif %}{% if account.name.last is not empty %} {{
|
||||
account.name.last|slice(0, 1)|upper }}.{% endif %}{% if account.name.second is not empty %} {{
|
||||
account.name.second }}{% endif %}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
36
mirzaev/ebala/system/views/pages/account.html
Normal file
36
mirzaev/ebala/system/views/pages/account.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends('index.html') %}
|
||||
|
||||
{% block css %}
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/list.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/pages/account.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<section id="account" class="panel small list">
|
||||
<section id="information">
|
||||
<!-- <h1>{% if account.name.first is not empty %}{{ account.name.first }}{% endif %}{% if account.name.second is not
|
||||
empty
|
||||
%} {{ account.name.second }}{% endif %}{% if account.name.last is not empty %} {{ account.name.last }}{% endif %}
|
||||
</h1> -->
|
||||
<h2>{{ account.getKey() }}</h2>
|
||||
</section>
|
||||
<section id="balance">
|
||||
<p><b>Баланс:</b> <span>{{ balance ?? 0 }}</span></p>
|
||||
</section>
|
||||
<section id="history">
|
||||
{% for entry in history %}
|
||||
<p class="{% if entry.task.result.processed %}processed{% endif %}"><b>{{ entry.task._key }}</b><span>{{
|
||||
entry.task.work }}</span><span>{{ entry.task.result.hours ?? 0 }} часов</span><span>{{ entry.task.result.hour ??
|
||||
0 * entry.task.result.hours ?? 0 }}р.</span><span>{{ entry.task.result.penalty ?? 0 }}р (штраф)</span><span>{{
|
||||
entry.task.result.bonus ?? 0 }}р. (бонус)</span><span>{{ entry.task.result.hour ?? 0 * entry.task.result.hours
|
||||
?? 0 + entry.task.result.penalty ?? 0 + entry.task.result.bonus ?? 0 }}р. (итого)</span></p>
|
||||
|
||||
{% endfor %}
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/javascript" data-reinitializer-once="true" src="/js/imask-7.1.0-alpha.js" defer></script>
|
||||
<script type="text/javascript" src="/js/account.js" defer></script>
|
||||
{% endblock %}
|
@@ -25,8 +25,8 @@
|
||||
class="icon arrow right"></i></button>
|
||||
<datalist id="markets">
|
||||
{% for account in accounts %}
|
||||
<option value="{{ account.account.getKey() }}">{{ account.account.getKey()}} {{
|
||||
account.account.name.first }} {{ account.account.name.second }}</option>
|
||||
<option value="{{ account.market.id }}">{{ account.market.id }} {{
|
||||
account.market.name.first }} {{ account.market.name.second }}</option>
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
</label>
|
||||
@@ -74,7 +74,7 @@
|
||||
fields.password.button = fields.password.label.getElementsByTagName('button')[0];
|
||||
|
||||
// Инициализация маски идентификатора магазина
|
||||
fields.market.input.mask = IMask(fields.market.input, {mask: '000000000000'});
|
||||
fields.market.input.mask = IMask(fields.market.input, {mask: '000'});
|
||||
|
||||
/**
|
||||
* Отправить входной псевдоним на сервер
|
||||
|
@@ -3,7 +3,6 @@
|
||||
{% block css %}
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/list.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/pages/markets.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/icons/cart.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/icons/nametag.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/icons/user_add.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/icons/work_alt.css">
|
||||
|
138
mirzaev/ebala/system/views/pages/settings.html
Normal file
138
mirzaev/ebala/system/views/pages/settings.html
Normal file
@@ -0,0 +1,138 @@
|
||||
{% extends('index.html') %}
|
||||
|
||||
{% block css %}
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/list.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/icons/user.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/icons/shopping_cart.css">
|
||||
<link type="text/css" rel="stylesheet" data-reinitializer-once="true" href="/css/pages/settings.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<section id="settings" class="panel">
|
||||
<section id="market_hour" class="category">
|
||||
<div class="unselectable">
|
||||
<div>
|
||||
<h2>Счёт магазину</h2><i class="icon bold shopping cart"></i>
|
||||
</div>
|
||||
<small>Цена выставляемая магазину за 1 час работы сотрудника</small>
|
||||
</div>
|
||||
{% for city, _settings in settings.market_hour|settings_by_city %}
|
||||
<section class="subcategory">
|
||||
<h3 class="unselectable">{{ city }}</h3>
|
||||
<div class="divider separator"></div>
|
||||
{% for hour in _settings %}
|
||||
<label>
|
||||
<span class="unselectable separated right">{{ hour.work }}</span>
|
||||
<input id="{{ hour._key }}" type="number" placeholder="0" value="{{ hour.value ?? 0 }}" min="0" max="9999"
|
||||
step="0.1" onkeypress="return core.numbers(event)"
|
||||
onkeyup="settings.write(this.getAttribute('id'), this.value)"
|
||||
oninput="settings.write(this.getAttribute('id'), this.value)">
|
||||
</label>
|
||||
{% else %}
|
||||
<p class="empty">Не найдено</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section id="worker_hour" class="category">
|
||||
<div class="unselectable">
|
||||
<div>
|
||||
<h2>Выплата сотруднику</h2><i class="icon bold user"></i>
|
||||
</div>
|
||||
<small>Количество заработной платы выплачиваемой сотруднику за 1 час его работы</small>
|
||||
</div>
|
||||
{% for city, _settings in settings.worker_hour|settings_by_city %}
|
||||
<section class="subcategory">
|
||||
<h3 class="unselectable">{{ city }}</h3>
|
||||
{% for hour in _settings %}
|
||||
<label>
|
||||
<span class="unselectable separated right">{{ hour.work }}</span>
|
||||
<input id="{{ hour._key }}" type="number" placeholder="0" value="{{ hour.value ?? 0 }}" min="0" max="9999"
|
||||
step="0.1" onkeypress="return core.numbers(event)"
|
||||
onkeyup="settings.write(this.getAttribute('id'), this.value)"
|
||||
oninput="settings.write(this.getAttribute('id'), this.value)">
|
||||
</label>
|
||||
{% else %}
|
||||
<p class="empty">Не найдено</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section id="worker_bonus" class="category">
|
||||
<div class="unselectable">
|
||||
<div>
|
||||
<h2>Премия сотруднику</h2><i class="icon bold user"></i>
|
||||
</div>
|
||||
<small>Количество добавления к заработной плате выплачиваемой сотруднику относительно оценки проделанной им работы
|
||||
на
|
||||
заявке магазином</small>
|
||||
</div>
|
||||
<section class="subcategory">
|
||||
{% for bonus in settings.worker_bonus %}
|
||||
<label>
|
||||
<span class="unselectable separated right">{{ bonus.rating }}</span>
|
||||
<input id="{{ bonus._key }}" type="number" placeholder="0" value="{{ bonus.value ?? 0 }}" min="0" max="9999"
|
||||
step="0.1" onkeypress="return core.numbers(event)"
|
||||
onkeyup="settings.write(this.getAttribute('id'), this.value)"
|
||||
oninput="settings.write(this.getAttribute('id'), this.value)">
|
||||
</label>
|
||||
{% else %}
|
||||
<p class="empty">Не найдено</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="worker_penalty" class="category">
|
||||
<div class="unselectable">
|
||||
<div>
|
||||
<h2>Штраф сотруднику</h2><i class="icon bold user"></i>
|
||||
</div>
|
||||
<small>Количество убавления от заработной платы выплачиваемой сотруднику относительно оценки проделанной им работы
|
||||
на заявке магазином</small>
|
||||
</div>
|
||||
<section class="subcategory">
|
||||
{% for penalty in settings.worker_penalty %}
|
||||
<label>
|
||||
<span class="unselectable separated right">{{ penalty.rating }}</span>
|
||||
<input id="{{ penalty._key }}" type="number" placeholder="0" value="{{ penalty.value ?? 0 }}" min="-9999" max="1"
|
||||
step="0.1" onkeypress="return core.numbers(event)"
|
||||
onkeyup="settings.write(this.getAttribute('id'), this.value)"
|
||||
oninput="settings.write(this.getAttribute('id'), this.value)">
|
||||
</label>
|
||||
{% else %}
|
||||
<p class="empty">Не найдено</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="worker_tax" class="category">
|
||||
<div class="unselectable">
|
||||
<div>
|
||||
<h2>Налоги за работу сотрудника</h2><i class="icon bold user"></i>
|
||||
</div>
|
||||
<small>Количество вычитаемое из заработной платы выплачиваемой сотруднику на уплату налогов</small>
|
||||
</div>
|
||||
<section class="subcategory">
|
||||
{% for tax in settings.worker_tax %}
|
||||
<label>
|
||||
<span class="unselectable separated right">{{ tax.registration }}</span>
|
||||
<input id="{{ tax._key }}" type="number" placeholder="0" value="{{ tax.value ?? 0 }}" min="0" max="9999"
|
||||
step="0.1" onkeypress="return core.numbers(event)"
|
||||
onkeyup="settings.write(this.getAttribute('id'), this.value)"
|
||||
oninput="settings.write(this.getAttribute('id'), this.value)">
|
||||
</label>
|
||||
{% else %}
|
||||
<p class="empty">Не найдено</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/javascript" data-reinitializer-once="true" src="/js/imask-7.1.0-alpha.js" defer></script>
|
||||
<script type="text/javascript" src="/js/settings.js" defer></script>
|
||||
{% endblock %}
|
@@ -20,8 +20,15 @@
|
||||
{% if account.type == 'administrator' or account.type == 'operator' or account.type == 'market' %}
|
||||
<button class="grass dense" onclick="tasks.create()">Создать</button>
|
||||
{% endif %}
|
||||
{% if account.type == 'administrator' or account.type == 'operator' %}
|
||||
<button class="sea" onclick="">Выгрузка</button>
|
||||
{% if account.type == 'administrator' or (account.type == 'operator' and account.transactions) %}
|
||||
<div>
|
||||
<button class="sea merged right" onclick="payments.workers()">Сотрудники</button>
|
||||
<button class="river merged left" onclick="payments.confirm('workers')">Подтвердить</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="sea merged right" onclick="payments.markets()">Магазины</button>
|
||||
<button class="river merged left" onclick="payments.confirm('markets')">Подтвердить</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</label>
|
||||
</form>
|
||||
@@ -89,7 +96,7 @@
|
||||
<span data-column="date" class="button">Дата</span>
|
||||
<span data-column="worker" class="button" title="Сотрудник"><i class="icon bold user"></i></span>
|
||||
<span data-column="name" class="button">ФИО</span>
|
||||
<span data-column="task" class="button">Работа</span>
|
||||
<span data-column="work" class="button">Работа</span>
|
||||
<span data-column="start" class="button" title="Начало"><i class="icon work alt"></i></span>
|
||||
<span data-column="end" class="button" title="Окончание"><i class="icon home"></i></span>
|
||||
<span data-column="hours" class="button" title="Время работы"><i class="icon timer"></i></span>
|
||||
@@ -145,4 +152,5 @@
|
||||
<script type="text/javascript" src="/js/workers.js" defer></script>
|
||||
<script type="text/javascript" src="/js/markets.js" defer></script>
|
||||
<script type="text/javascript" src="/js/chat.js" defer></script>
|
||||
<script type="text/javascript" src="/js/payments.js" defer></script>
|
||||
{% endblock %}
|
||||
|
@@ -58,7 +58,7 @@
|
||||
<span data-column="worker" class="button" title="Сотрудник"><i class="icon bold user"></i></span>
|
||||
<span data-column="name" class="button">ФИО</span>
|
||||
<span data-column="number" class="button">Номер</span>
|
||||
<span data-column="mail" class="button">Почта</span>
|
||||
<span data-column="work" class="button">Работа</span>
|
||||
<span data-column="address" class="button">Адрес</span>
|
||||
<span data-column="passport" class="button">Паспорт</span>
|
||||
<span data-column="tax" class="button">ИНН</span>
|
||||
|
@@ -59,7 +59,7 @@ final class templater extends controller implements ArrayAccess
|
||||
}
|
||||
if (!empty($account->status())) $this->twig->addGlobal('account', $account);
|
||||
|
||||
// Инициализация фильтров
|
||||
// Инициализация фильтра
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'storaged_number_to_readable',
|
||||
@@ -67,7 +67,7 @@ final class templater extends controller implements ArrayAccess
|
||||
)
|
||||
);
|
||||
|
||||
// Инициализация фильтров
|
||||
// Инициализация фильтра
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'storaged_requisites_to_card',
|
||||
@@ -78,7 +78,7 @@ final class templater extends controller implements ArrayAccess
|
||||
)
|
||||
);
|
||||
|
||||
// Инициализация фильтров
|
||||
// Инициализация фильтра
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'storaged_requisites_preview',
|
||||
@@ -86,7 +86,7 @@ final class templater extends controller implements ArrayAccess
|
||||
)
|
||||
);
|
||||
|
||||
// Инициализация фильтров
|
||||
// Инициализация фильтра
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'date_to_russian',
|
||||
@@ -94,7 +94,7 @@ final class templater extends controller implements ArrayAccess
|
||||
)
|
||||
);
|
||||
|
||||
// Инициализация фильтров
|
||||
// Инициализация фильтра
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'account_type_to_russian',
|
||||
@@ -108,6 +108,26 @@ final class templater extends controller implements ArrayAccess
|
||||
)
|
||||
);
|
||||
|
||||
// Инициализация фильтра
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'work',
|
||||
fn (string $work) => preg_replace('/^Мобильный/', 'Моб.', $work)
|
||||
)
|
||||
);
|
||||
|
||||
// Инициализация фильтра
|
||||
$this->twig->addFilter(
|
||||
new TwigFilter(
|
||||
'settings_by_city',
|
||||
function (array|null $settings = []) {
|
||||
$result = [];
|
||||
foreach($settings ?? [] as $setting) $result[$setting['city']][] = $setting;
|
||||
return $result;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Инициализация расширений
|
||||
$this->twig->addExtension(new intl());
|
||||
}
|
||||
|
Reference in New Issue
Block a user