5 Commits

16 changed files with 4343 additions and 3594 deletions

View File

@@ -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;
}
/**
* Прочитать данные
*
@@ -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,

View File

@@ -359,7 +359,7 @@ final class market extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных магазина
$market = model::read('d.id == "' . $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)) {
// Найдены данные магазина
@@ -388,6 +388,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;
}
/**
* Обновить данные
*
@@ -399,7 +559,7 @@ final class market extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных магазина
$market = model::read('d.id == "' . $parameters['id'] . '"');
$market = model::read('d.id == "' . urldecode($parameters['id']) . '"');
if (!empty($market)) {
// Найден магазин
@@ -416,6 +576,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)) {
// Записаны данные магазина

View File

@@ -76,7 +76,7 @@ final class payments extends core
} else throw new exception('Вы не авторизованы');
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors[] = [
$this->errors['export'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -110,7 +110,7 @@ final class payments extends core
/**
* Магазины
*
* Расчитать ... (сверку?) за выбранный период и сгенерировать excel-документ
* Расчитать прибыль с магазинов за выбранный период и сгенерировать excel-документ
*
* @param array $parameters Параметры запроса
*
@@ -161,7 +161,7 @@ final class payments extends core
} else throw new exception('Вы не авторизованы');
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors[] = [
$this->errors['export'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -192,4 +192,74 @@ final class payments extends core
}
}
/**
* Подтвердить
*
* Подтвердить выполнение операций с документом (магазины или сотрудники)
*
* @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->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();
}
}
}

View File

@@ -11,6 +11,7 @@ use mirzaev\ebala\controllers\core,
mirzaev\ebala\models\account,
mirzaev\ebala\models\worker,
mirzaev\ebala\models\market,
mirzaev\ebala\models\payments,
mirzaev\ebala\models\core as _core;
// Библиотека для ArangoDB
@@ -557,15 +558,15 @@ final class task extends core
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже начата?
if (time() - $start > 0)
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено редактировать начатую заявку');
// Заявка уже прошла?
if (time() - $end > 0)
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено редактировать прошедшую заявку');
// Заявка уже завершена?
if ($task->completed === true)
else if ($task->completed === true)
throw new exception('Запрещено редактировать завершённую заявку');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)
@@ -577,7 +578,7 @@ final class task extends core
throw new exception('Запрещено редактировать заявку за менее 16 часов до её начала');
}
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -860,7 +861,7 @@ final class task extends core
// Перевод ключей на русский язык
foreach ($this->view->task as $key => $value)
if ($key === 'updates')
foreach ($value as $key => $value) $buffer['updates'][$key] = [
foreach ($value ?? [] as $key => $value) $buffer['updates'][$key] = [
'label' => match ($key) {
'operator' => 'Оператор',
'market' => 'Магазин',
@@ -1207,7 +1208,7 @@ final class task extends core
// Изменение статуса подтверждения
$task->confirmed = !$task->confirmed;
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -1332,7 +1333,7 @@ final class task extends core
}
}
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -1453,7 +1454,7 @@ final class task extends core
else {
// Получена оценка
// Запись оценики
// Запись оценки
$task->rating = $parameters['rating'];
if (!empty($parameters['review'])) {
@@ -1468,9 +1469,48 @@ final class task extends core
// Снятие с публикации
$task->published = false;
// Иниализация сотрудника
$worker = worker::read('d.id == "' . $task->worker . '"');
if ($worker instanceof _document) {
// Найден сотрудник
// Инициализация магазина
$market = market::read('d.id == "' . $task->market . '"');
if ($market instanceof _document) {
// Найден магазин
// Подсчёт часов работы
$hours = model::hours($task->start, $task->end, $this->errors);
// Инициализация цены работы сотрудника за 1 час
$hour = payments::hour('worker', $market->city, $task->work);
// Подсчёт оплаты за работу
$payment = $hour * $hours;
// Инициализация штрафа
$penalty = payments::penalty($task->rating ?? null);
if ($penalty === null) $penalty = -$payment;
// Инициализация премии
$bonus = payments::bonus($task->rating ?? null);
// Запись результатов
$task->result = [
'hours' => $hours,
'hour' => $hour,
'penalty' => $penalty,
'bonus' => $bonus,
'processed' => false
];
}
}
}
// Запись в ресстре последних обновивших
// Запись в реcстре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -1567,7 +1607,7 @@ final class task extends core
// Изменение статуса скрытия
$task->hided = !$task->hided;
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -1679,15 +1719,15 @@ final class task extends core
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже начата?
if (time() - $start > 0)
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено удалять начатую заявку');
// Заявка уже прошла?
if (time() - $end > 0)
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено удалять прошедшую заявку');
// Заявка уже завершена?
if ($task->completed === true)
else if ($task->completed === true)
throw new exception('Запрещено удалять завершённую заявку');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)
@@ -1705,7 +1745,7 @@ final class task extends core
// Изменение статуса
$task->status = 'deleted';
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -2047,24 +2087,40 @@ final class task extends core
// Заявка подтверждена?
if ($task->confirmed) throw new exception('Запрещено редактировать тип работы у подтверждённой заявки');
// Инициализация даты
$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));
if ($this->account->type === 'market') {
// Магазин
// Инициализация времени
$start = datetime::createFromFormat('H:i', (string) $task->start);
$end = datetime::createFromFormat('H:i', (string) $task->end);
// Инициализация даты
$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));
// Перенос времени в дату
$start = $date->setTime((int) $start->format('H'), (int) $start->format('i'))->format('U');
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Инициализация времени
$start = datetime::createFromFormat('H:i', (string) $task->start);
$end = datetime::createFromFormat('H:i', (string) $task->end);
// Заявка уже начата
if ($this->account->type === 'market' and time() - $start > 0)
throw new exception('Запрещено редактировать тип работы начатой заявки');
// Перенос времени в дату
$start = $date->setTime((int) $start->format('H'), (int) $start->format('i'))->format('U');
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже завершена
if ($this->account->type === 'market' and $task->completed === true || time() - $end > 0)
throw new exception('Запрещено редактировать тип работы завершённой заявки');
// Заявка уже начата?
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено редактировать тип работы начатой заявки');
// Заявка уже прошла?
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено редактировать тип работы прошедшей заявки');
// Заявка уже завершена?
else if ($task->completed === true)
throw new exception('Запрещено редактировать тип работы завершённой заявки');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)
/* if (time() - $task->created > 1800)
throw new exception('Запрещено редактировать заявку спустя 30 минут после создания'); */
// До начала заявки осталось менее 16 часов? (57600 секунд = 16 часов)
if ($start - time() < 57600)
throw new exception('Запрещено редактировать тип работы заявки за менее 16 часов до её начала');
}
if ($task instanceof _document) {
// Найдена заявка
@@ -2075,7 +2131,7 @@ final class task extends core
default => 'Кассир'
};
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -2197,7 +2253,7 @@ final class task extends core
// Изменение статуса
$task->description = $parameters['description'];
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -2287,24 +2343,40 @@ final class task extends core
// Заявка подтверждена?
if ($task->confirmed) throw new exception('Запрещено редактировать дату и время у подтверждённой заявки');
// Инициализация даты
$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));
if ($this->account->type === 'market') {
// Магазин
// Инициализация времени
$start = datetime::createFromFormat('H:i', (string) $task->start);
$end = datetime::createFromFormat('H:i', (string) $task->end);
// Инициализация даты
$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));
// Перенос времени в дату
$start = $date->setTime((int) $start->format('H'), (int) $start->format('i'))->format('U');
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Инициализация времени
$start = datetime::createFromFormat('H:i', (string) $task->start);
$end = datetime::createFromFormat('H:i', (string) $task->end);
// Заявка уже начата
if ($this->account->type === 'market' and time() - $start > 0)
throw new exception('Запрещено редактировать дату и время начатой заявки');
// Перенос времени в дату
$start = $date->setTime((int) $start->format('H'), (int) $start->format('i'))->format('U');
$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'))->format('U');
// Заявка уже завершена
if ($this->account->type === 'market' and $task->completed === true || time() - $end > 0)
throw new exception('Запрещено редактировать дату и время завершённой заявки');
// Заявка уже начата?
if (time() - $start > 0 && time() - $end < 0)
throw new exception('Запрещено редактировать дату и время начатой заявки');
// Заявка уже прошла?
else if (time() - $end > 0 && $task->completed !== true)
throw new exception('Запрещено редактировать дату и время прошедшей заявки');
// Заявка уже завершена?
else if ($task->completed === true)
throw new exception('Запрещено редактировать дату и время завершённой заявки');
// Прошло более 30 минут после создания заявки? (1800 секунд = 30 минут)
/* if (time() - $task->created > 1800)
throw new exception('Запрещено редактировать заявку спустя 30 минут после создания'); */
// До начала заявки осталось менее 16 часов? (57600 секунд = 16 часов)
if ($start - time() < 57600)
throw new exception('Запрещено редактировать дату и время заявки за менее 16 часов до её начала');
}
if ($task instanceof _document) {
// Найдена заявка
@@ -2314,7 +2386,7 @@ final class task extends core
if (!empty($parameters['start'])) $task->start = $parameters['start'];
if (!empty($parameters['end'])) $task->end = $parameters['end'];
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -2407,7 +2479,7 @@ final class task extends core
// Запись комментария
$task->commentary = $parameters['commentary'];
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -2500,7 +2572,7 @@ final class task extends core
// Запись статуса о публикации
$task->published = true;
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -2593,7 +2665,7 @@ final class task extends core
// Запись статуса о публикации
$task->published = false;
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -2948,7 +3020,7 @@ final class task extends core
$task->problematic = false;
}
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()
@@ -3026,7 +3098,7 @@ final class task extends core
$task->problematic = false;
}
// Запись в ресстре последних обновивших
// Запись в реестре последних обновивших
$task->updates = [$this->account->type => match ($this->account->type) {
'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
default => $this->account->getKey()

View File

@@ -146,41 +146,55 @@ final class worker extends core
? null
: <<<AQL
SEARCH
a.commentary IN TOKENS(@search, 'text_ru')
|| a.address IN TOKENS(@search, 'text_ru')
|| a.passport IN TOKENS(@search, 'text_ru')
|| a.department.address IN TOKENS(@search, 'text_ru')
|| a.requisites IN TOKENS(@search, 'text_ru')
b.commentary IN TOKENS(@search, 'text_ru')
|| b.address IN TOKENS(@search, 'text_ru')
|| b.passport IN TOKENS(@search, 'text_ru')
|| b.department.address IN TOKENS(@search, 'text_ru')
|| b.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)
|| STARTS_WITH(a.address, @search)
|| STARTS_WITH(a.city, @search)
|| STARTS_WITH(a.district, @search)
|| STARTS_WITH(b._key, @search)
|| STARTS_WITH(b.id, @search)
|| STARTS_WITH(b.name.first, @search)
|| STARTS_WITH(b.name.second, @search)
|| STARTS_WITH(b.name.last, @search)
|| STARTS_WITH(b.address, @search)
|| STARTS_WITH(b.city, @search)
|| STARTS_WITH(b.district, @search)
|| STARTS_WITH(a.number, @search)
|| STARTS_WITH(b.number, @search)
|| STARTS_WITH(a.mail, @search)
|| STARTS_WITH(a.passport, @search)
|| STARTS_WITH(a.department.number, @search)
|| STARTS_WITH(a.department.address, @search)
|| STARTS_WITH(a.requisites, @search)
|| STARTS_WITH(a.tax, @search)
|| STARTS_WITH(b.mail, @search)
|| STARTS_WITH(b.passport, @search)
|| STARTS_WITH(b.department.number, @search)
|| STARTS_WITH(b.department.address, @search)
|| STARTS_WITH(b.requisites, @search)
|| STARTS_WITH(b.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))
|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(a.address, TOKENS(@search, 'text_ru')[0], 2, true))
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.city, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.district, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(b._key, TOKENS(@search, 'text_en')[0], 2, true))
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(b.id, TOKENS(@search, 'text_en')[0], 1, true))
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(b.name.first, TOKENS(@search, 'text_ru')[0], 2, true))
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(b.name.second, TOKENS(@search, 'text_ru')[0], 2, true))
|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(b.name.last, TOKENS(@search, 'text_ru')[0], 2, true))
|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(b.address, TOKENS(@search, 'text_ru')[0], 2, true))
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(b.city, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(b.district, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.number, TOKENS(@search, 'text_en')[0], 2, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(b.number, TOKENS(@search, 'text_en')[0], 2, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.mail, TOKENS(@search, 'text_en')[0], 2, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.passport, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(a.department.number, TOKENS(@search, 'text_en')[0], 1, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.department.address, TOKENS(@search, 'text_ru')[0], 1, true))
|| (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))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(b.mail, TOKENS(@search, 'text_en')[0], 2, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(b.passport, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(b.department.number, TOKENS(@search, 'text_en')[0], 1, true))
|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(b.department.address, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(b.requisites, TOKENS(@search, 'text_ru')[0], 1, true))
|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(b.tax, TOKENS(@search, 'text_ru')[0], 1, true))
OPTIONS { collections: ["account", "worker"] }
AQL;
@@ -396,7 +410,7 @@ final class worker extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных сотрудника
$worker = model::read('d.id == "' . $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}')->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}')->getAll();
if (!empty($worker)) {
// Найдены данные сотрудника
@@ -436,7 +450,7 @@ final class worker extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных сотрудника
$worker = model::read('d.id == "' . $parameters['id'] . '"');
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"');
if (!empty($worker)) {
// Найден сотрудник
@@ -533,7 +547,7 @@ final class worker extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных сотрудника
$worker = model::read('d.id == "' . $parameters['id'] . '"');
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"');
if (!empty($worker)) {
// Найден сотрудник
@@ -587,7 +601,7 @@ final class worker extends core
// Авторизован аккаунт администратора или оператора
// Инициализация данных сотрудника
$worker = model::read('d.id == "' . $parameters['id'] . '"');
$worker = model::read('d.id == "' . urldecode($parameters['id']) . '"');
if (!empty($worker)) {
// Найден сотрудник

View File

@@ -46,7 +46,7 @@ final class payments extends core
{
try {
// Чтение заявок
$tasks = @task::read("d.date >= $from && d.date <= $to && d.problematic == false && d.completed == 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);
$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) {
// Найдены заявки
@@ -206,11 +206,11 @@ final class payments extends core
->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($market->city, $task->work))
->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("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 = static::penalty($task->rating ?? null)) === null ? $payment : $penalty) + $bonus)
->setCellValue("R$row", $payment + ($penalty === null ? -$payment : $penalty) + $bonus)
->setCellValue("S$row", '')
->setCellValue("T$row", $worker->payment) // Наличные?
->setCellValue("U$row", '')
@@ -253,7 +253,7 @@ final class payments extends core
/**
* Магазины
*
* Расчитать ... и сгенерировать excel-документ
* Расчитать прибыль с магазинов и сгенерировать excel-документ
*
* @param int $from Начальная дата для выборки заявок (unixtime)
* @param int $to Конечная дата для выборки заявок (unixtime)
@@ -431,6 +431,15 @@ final class payments extends core
// Инициализация счётчика строк
$row = 9;
// Инициализация буфера объединённых данных всех магазинов
$total = [
'workers' => 0,
'hours' => 0,
'hour' => [],
'payment' => 0,
'vat' => 0
];
foreach ($merged as $id => $dates) {
// Перебор магазинов
@@ -465,7 +474,7 @@ final class payments extends core
->setCellValue("E$row", $work)
->setCellValue("F$row", $task['workers'])
->setCellValue("G$row", $task['hours'])
->setCellValue("H$row", $hour = static::hour($market->city, $work))
->setCellValue("H$row", $hour = static::hour('market', $market->city, $work))
->setCellValue("I$row", $payment = $hour * $task['hours'])
->setCellValue("J$row", $payment);
@@ -479,7 +488,15 @@ final class payments extends core
// Инкрементация счётчика для генерации следующей строки
++$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
@@ -491,10 +508,17 @@ final class payments extends core
->setCellValue("E$row", '')
->setCellValue("F$row", $result['workers'])
->setCellValue("G$row", $result['hours'])
->setCellValue("H$row", array_sum($result['hour']) / count($result['hour']))
->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()
@@ -508,6 +532,36 @@ final class payments extends core
}
}
// Запись строки с общими данными всех магазинов
$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');
@@ -529,50 +583,142 @@ final class payments extends core
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 $city, string $work): int|float
public static function hour(string $type, string $city, string $work): int|float
{
return match ($city) {
'Красноярск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 257.07,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 257.07,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 257.07,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
return
match (mb_strtolower($type)) {
'market', 'магазин' => match (mb_strtolower($city)) {
'красноярск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 257.07,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 257.07,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 257.07,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
'железногорск', 'сосновоборск', 'тыва' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 263.34,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 263.34,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 263.34,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
'хакасия', 'иркутск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 245.385,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 245.385,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 245.385,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
default => 0
},
'worker', 'сотрудник' => match (mb_strtolower($city)) {
'красноярск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 190.91,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 190.91,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 190.91,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 250,
'loaders', 'loader', 'грузчики', 'грузчик' => 177.27,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 250,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 250,
default => 0
},
'железногорск', 'сосновоборск', 'тыва' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 190.91,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 190.91,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 190.91,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 250,
'loaders', 'loader', 'грузчики', 'грузчик' => 177.27,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 250,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 250,
default => 0
},
'хакасия', 'иркутск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 181.82,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 181.82,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 181.82,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 250,
'loaders', 'loader', 'грузчики', 'грузчик' => 168.18,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 250,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 250,
default => 0
},
default => 0
},
default => 0
},
'Железногорск', 'Сосновоборск', 'Тыва' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 263.34,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 263.34,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 263.34,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
'Хакасия', 'Иркутск' => match (mb_strtolower($work)) {
'cashiers', 'cashier', 'кассиры', 'кассир' => 245.385,
'displayers', 'displayer', 'выкладчики', 'выкладчик' => 245.385,
'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 245.385,
'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 360,
'loaders', 'loader', 'грузчики', 'грузчик' => 255.645,
'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 305,
'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 305,
default => 0
},
default => 0
};
};
}
/**

View File

@@ -245,4 +245,51 @@ final class task extends core
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;
}
}

View File

@@ -44,6 +44,12 @@ div#popup>section.list>div.row.endless {
height: auto;
}
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;

View File

@@ -49,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');
@@ -103,6 +106,7 @@ $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');
// Инициализация ядра
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));

View File

@@ -185,10 +185,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 +195,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

View File

@@ -52,8 +52,8 @@ if (typeof window.payments !== "function") {
if (header !== null) {
// Найден заголовок (подразумевается, что передан файл, а не случайная ошибка)
// Инициализация названия файла
// Инициализация названия файла
const name = header.split(";")[1].split("=")[1];
// Чтение полученного файла (подразумевается ошибка при инициализации json)
@@ -129,7 +129,7 @@ if (typeof window.payments !== "function") {
if (header !== null) {
// Найден заголовок (подразумевается, что передан файл, а не случайная ошибка)
// Инициализация названия файла
// Инициализация названия файла
const name = header.split(";")[1].split("=")[1];
// Чтение полученного файла (подразумевается ошибка при инициализации json)
@@ -157,6 +157,53 @@ if (typeof window.payments !== "function") {
});
}, 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-элемент со списком ошибок
*

View File

@@ -2210,34 +2210,6 @@ if (typeof window.tasks !== "function") {
});
});
// Инициализация оболочки для кнопок
const buttons_worker = document.createElement("div");
buttons_worker.classList.add("row", "buttons", "divided", "merged");
// Инициализация кнопки подтверждения
const ban = document.createElement("button");
if (true) {
// Забанен сотрудник
ban.classList.add("earth", "stretched");
ban.innerText = "Разблокировать";
ban.setAttribute('title', 'Сотрудник снова сможет записываться на ваши заявки');
ban.setAttribute(
"onclick",
`tasks.unban(this, this.parentElement.previousElementSibling.previousElementSibling.children[0], this.parentElement.previousElementSibling, document.getElementById('${task}'))`
);
} else {
// Не забанен сотрудник
ban.classList.add("clay", "stretched");
ban.innerText = "Заблокировать";
ban.setAttribute('title', 'Сотрудник не сможет записываться на ваши заявки');
ban.setAttribute(
"onclick",
`tasks.ban(this, this.parentElement.previousElementSibling.previousElementSibling.children[0], this.parentElement.previousElementSibling, document.getElementById('${task}'))`
);
}
// Инициализация оболочки для кнопок
const buttons = document.createElement("div");
buttons.classList.add("row", "buttons", "merged");
@@ -2273,7 +2245,7 @@ if (typeof window.tasks !== "function") {
complete.innerText = "Завершить";
complete.setAttribute(
"onclick",
`tasks.complete(this, this.parentElement.previousElementSibling.previousElementSibling.children[0], this.parentElement.previousElementSibling, document.getElementById('${task}'))`
`tasks.complete(this, this.parentElement.previousElementSibling.previousElementSibling.previousElementSibling.children[0], this.parentElement.previousElementSibling.previousElementSibling, document.getElementById('${task}'))`
);
// Инициализация окна с ошибками
@@ -2325,8 +2297,61 @@ if (typeof window.tasks !== "function") {
if (data.completed !== true) {
// Зявка не завершена
buttons_worker.appendChild(ban);
column.appendChild(buttons_worker);
if (typeof window.markets === "function") {
// Инициализирована программа-обработчик магазинов (подразумевается)
// Инициализация оболочки для кнопок
const buttons_worker = document.createElement("div");
buttons_worker.classList.add(
"row",
"buttons",
"divided",
"merged"
);
// Инициализация кнопки подтверждения
const ban = document.createElement("button");
// Инициализация идентификатора сотрудника
const worker = row.querySelector(
'[data-column="worker"]'
).innerText;
markets.banned(worker).then((banned) => {
if (banned) {
// Забанен сотрудник
ban.classList.add("earth", "stretched");
ban.innerText = "Разблокировать";
ban.setAttribute(
"title",
"Сотрудник снова сможет записываться на заявки"
);
ban.setAttribute(
"onclick",
`markets.unban(${worker}, this)`
);
} else {
// Не забанен сотрудник
ban.classList.add("clay", "stretched");
ban.innerText = "Заблокировать";
ban.setAttribute(
"title",
"Сотрудник не сможет записываться на заявки"
);
ban.setAttribute(
"onclick",
`markets.ban(${worker}, this)`
);
}
buttons_worker.appendChild(ban);
column.insertBefore(buttons_worker, buttons);
top(this.body.errors);
});
}
buttons.appendChild(problem);
buttons.appendChild(complete);
@@ -2606,6 +2631,10 @@ if (typeof window.tasks !== "function") {
work.classList.add("row", "connected", "stretched");
this.works(task).then((html) => (work.innerHTML = html));
work.setAttribute("title", "Тип работы");
work.setAttribute(
"onchange",
`tasks.work(document.getElementById('${task}'), this)`
);
// Инициализация поля ввода описания
const description = document.createElement("textarea");
@@ -2938,8 +2967,18 @@ if (typeof window.tasks !== "function") {
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
this.freeze = true;
// Активация виртуальной кнопки "подтвердить"
confirm.click();
if (
typeof core === "function" &&
core.interface === "operator" &&
core.interface === "administrator"
) {
// Оператор или администратор
// Активация виртуальной кнопки "подтвердить"
confirm.click();
} else {
// Магазин или сотрудник
}
// Возвращение статуса блокировки закрытия окна
this.freeze = freeze;
@@ -3783,8 +3822,6 @@ if (typeof window.tasks !== "function") {
// Инициализация идентификатора строки
const id = row.getAttribute("id");
alert(228);
if (typeof id === "string") {
// Инициализирован идентификатор

View File

@@ -31,8 +31,11 @@
</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>

View 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 %}

View File

@@ -21,8 +21,14 @@
<button class="grass dense" onclick="tasks.create()">Создать</button>
{% endif %}
{% if account.type == 'administrator' or account.type == 'operator' %}
<button class="sea" onclick="payments.workers()">Сотрудники</button>
<button class="sea" onclick="payments.markets()">Магазины</button>
<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>