From 4c6e5cdd1d89fcf383fa0839b7427cdec8883ab2 Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Thu, 29 Feb 2024 02:39:27 +0700 Subject: [PATCH] resolved #30, resolved #63 --- .../system/controllers/administrator.php | 6 +- mirzaev/ebala/system/controllers/index.php | 4 +- mirzaev/ebala/system/controllers/market.php | 6 +- mirzaev/ebala/system/controllers/operator.php | 6 +- mirzaev/ebala/system/controllers/payments.php | 195 ++++++ mirzaev/ebala/system/controllers/task.php | 14 +- mirzaev/ebala/system/controllers/worker.php | 6 +- mirzaev/ebala/system/models/core.php | 63 ++ mirzaev/ebala/system/models/payments.php | 609 ++++++++++++++++++ mirzaev/ebala/system/models/task.php | 49 +- mirzaev/ebala/system/public/css/list.css | 2 +- .../ebala/system/public/css/pages/tasks.css | 6 +- mirzaev/ebala/system/public/index.php | 2 + mirzaev/ebala/system/public/js/buffer.js | 4 +- mirzaev/ebala/system/public/js/chat.js | 4 +- mirzaev/ebala/system/public/js/damper.js | 4 +- mirzaev/ebala/system/public/js/payments.js | 274 ++++++++ mirzaev/ebala/system/public/js/tasks.js | 328 ++++++---- .../ebala/system/views/elements/tasks.html | 2 +- mirzaev/ebala/system/views/pages/tasks.html | 6 +- 20 files changed, 1441 insertions(+), 149 deletions(-) create mode 100755 mirzaev/ebala/system/controllers/payments.php create mode 100755 mirzaev/ebala/system/models/payments.php create mode 100644 mirzaev/ebala/system/public/js/payments.js diff --git a/mirzaev/ebala/system/controllers/administrator.php b/mirzaev/ebala/system/controllers/administrator.php index b528ba1..fb16e3a 100755 --- a/mirzaev/ebala/system/controllers/administrator.php +++ b/mirzaev/ebala/system/controllers/administrator.php @@ -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 diff --git a/mirzaev/ebala/system/controllers/index.php b/mirzaev/ebala/system/controllers/index.php index f408266..4030de5 100755 --- a/mirzaev/ebala/system/controllers/index.php +++ b/mirzaev/ebala/system/controllers/index.php @@ -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-элемента $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; diff --git a/mirzaev/ebala/system/controllers/market.php b/mirzaev/ebala/system/controllers/market.php index e87ac01..21e335a 100755 --- a/mirzaev/ebala/system/controllers/market.php +++ b/mirzaev/ebala/system/controllers/market.php @@ -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 diff --git a/mirzaev/ebala/system/controllers/operator.php b/mirzaev/ebala/system/controllers/operator.php index cd15289..cef225d 100755 --- a/mirzaev/ebala/system/controllers/operator.php +++ b/mirzaev/ebala/system/controllers/operator.php @@ -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 diff --git a/mirzaev/ebala/system/controllers/payments.php b/mirzaev/ebala/system/controllers/payments.php new file mode 100755 index 0000000..2ddfcc8 --- /dev/null +++ b/mirzaev/ebala/system/controllers/payments.php @@ -0,0 +1,195 @@ + + */ +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->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[] = [ + '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->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[] = [ + '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(); + } + } + +} diff --git a/mirzaev/ebala/system/controllers/task.php b/mirzaev/ebala/system/controllers/task.php index 30f14f2..c2fcb31 100755 --- a/mirzaev/ebala/system/controllers/task.php +++ b/mirzaev/ebala/system/controllers/task.php @@ -48,7 +48,7 @@ final class task extends core $this->errors['tasks'] ??= []; if (!empty($json = json_decode(file_get_contents('php://input'), true, 4))) { - + // Найден и декодирован json-документ с данными для создания заявок foreach ($json as $work => $tasks) { // Перебор категорий (колонок) @@ -58,7 +58,7 @@ final class task extends core // Создание заявки model::create( - work: model::label($work), + work: model::label($work), market: $this->account->type === 'market' ? account::market($this->account->getId())?->id : null, start: $task['start'], end: $task['end'], @@ -145,7 +145,7 @@ final class task 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; // Генерация AQL-выражения для инъекции в строку запроса if ($name === 'from') $interval .= " && task.date >= $value"; @@ -162,7 +162,7 @@ final class task 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; @@ -182,7 +182,7 @@ final class task extends core // Перебор фильтров по статусам // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) - if (empty($value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? 0)) continue; + if (empty($value = $_COOKIE["tasks_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? 0)) continue; // Конвертация ярлыков $converted = match ($name) { @@ -238,7 +238,7 @@ final class task extends core else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'])) $this->session->write(['tasks' => ['page' => 1]]); // Инициализация строки поиска - $search = $_COOKIE["tasks_filter_search"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['search'] ?? ''; + $search = $_COOKIE["tasks_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters']['search'] ?? ''; if (mb_strlen($search) < 3) $search = null; $search_query = empty($search) ? null @@ -407,6 +407,8 @@ final class task extends core * @param array $rows Строки * * @return array Обработанные строки + * + * @todo Переделать в model::hours($from, $to); */ protected static function preprocessing(account $account, array $rows): array { diff --git a/mirzaev/ebala/system/controllers/worker.php b/mirzaev/ebala/system/controllers/worker.php index 88111f1..8a295a1 100755 --- a/mirzaev/ebala/system/controllers/worker.php +++ b/mirzaev/ebala/system/controllers/worker.php @@ -44,7 +44,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 +94,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 +135,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 diff --git a/mirzaev/ebala/system/models/core.php b/mirzaev/ebala/system/models/core.php index 37a73bf..658cebc 100755 --- a/mirzaev/ebala/system/models/core.php +++ b/mirzaev/ebala/system/models/core.php @@ -132,6 +132,69 @@ 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 * diff --git a/mirzaev/ebala/system/models/payments.php b/mirzaev/ebala/system/models/payments.php new file mode 100755 index 0000000..9c46c72 --- /dev/null +++ b/mirzaev/ebala/system/models/payments.php @@ -0,0 +1,609 @@ + + */ +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", 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($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 = static::penalty($task->rating ?? null)) === 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; + + 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->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; + } + } + + // Запись строки с общими данными магазина + $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", array_sum($result['hour']) / count($result['hour'])) + ->setCellValue("I$row", $result['payment']) + ->setCellValue("J$row", $result['vat']); + + // Запись цвета строки с общими данными магазина + $spreadsheet + ->getActiveSheet() + ->getStyle("A$row:J$row") + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setARGB('ffdfe4ec'); + + ++$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; + } + + + /** + * Determine tariff + * + * @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 + { + 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, + 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 + }; + } + + /** + * 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 match ($rating) { + 5 => 100, + default => 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 + { + return match ($rating) { + 3 => -100, + 2 => -500, + 1 => null, + default => 0 + }; + } +} diff --git a/mirzaev/ebala/system/models/task.php b/mirzaev/ebala/system/models/task.php index 62c041d..b42c676 100755 --- a/mirzaev/ebala/system/models/task.php +++ b/mirzaev/ebala/system/models/task.php @@ -14,8 +14,9 @@ use mirzaev\arangodb\collection, // Библиотека для ArangoDB use ArangoDBClient\Document as _document; -// Встроенные библиотеки -use exception; +// System libraries +use datetime, + exception; /** * Модель заданий @@ -176,14 +177,54 @@ 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 * diff --git a/mirzaev/ebala/system/public/css/list.css b/mirzaev/ebala/system/public/css/list.css index b9af32e..828c80a 100755 --- a/mirzaev/ebala/system/public/css/list.css +++ b/mirzaev/ebala/system/public/css/list.css @@ -18,7 +18,7 @@ section.panel.list.medium { } section.panel.list > :is(form, search).row.menu { - margin-bottom: 10px;%s" + margin-bottom: 10px; transition: 0s; } diff --git a/mirzaev/ebala/system/public/css/pages/tasks.css b/mirzaev/ebala/system/public/css/pages/tasks.css index 96cdd79..3f2a98e 100755 --- a/mirzaev/ebala/system/public/css/pages/tasks.css +++ b/mirzaev/ebala/system/public/css/pages/tasks.css @@ -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"], @@ -61,14 +61,14 @@ section#tasks.panel.list > div.row > span[data-column="name"] { width: 130px; } -section#tasks.panel.list > div.row > span[data-column="task"] { +section#tasks.panel.list > div.row > span[data-column="work"] { min-width: 100px; width: 100px; } section#tasks.panel.list > div.row:not(:nth-of-type(1)) - > span[data-column="task"] { + > span[data-column="work"] { text-align: right; } diff --git a/mirzaev/ebala/system/public/index.php b/mirzaev/ebala/system/public/index.php index 75cea38..986b4f9 100755 --- a/mirzaev/ebala/system/public/index.php +++ b/mirzaev/ebala/system/public/index.php @@ -98,6 +98,8 @@ $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'); // Инициализация ядра $core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false)); diff --git a/mirzaev/ebala/system/public/js/buffer.js b/mirzaev/ebala/system/public/js/buffer.js index 67abee3..76d3e2e 100644 --- a/mirzaev/ebala/system/public/js/buffer.js +++ b/mirzaev/ebala/system/public/js/buffer.js @@ -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", diff --git a/mirzaev/ebala/system/public/js/chat.js b/mirzaev/ebala/system/public/js/chat.js index 821be63..9fd1276 100644 --- a/mirzaev/ebala/system/public/js/chat.js +++ b/mirzaev/ebala/system/public/js/chat.js @@ -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} */ diff --git a/mirzaev/ebala/system/public/js/damper.js b/mirzaev/ebala/system/public/js/damper.js index 70facee..4e7ff57 100755 --- a/mirzaev/ebala/system/public/js/damper.js +++ b/mirzaev/ebala/system/public/js/damper.js @@ -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); } }; diff --git a/mirzaev/ebala/system/public/js/payments.js b/mirzaev/ebala/system/public/js/payments.js new file mode 100644 index 0000000..04b9fc3 --- /dev/null +++ b/mirzaev/ebala/system/public/js/payments.js @@ -0,0 +1,274 @@ +"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); + + /** + * Сгенерировать 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 }, + }), +); diff --git a/mirzaev/ebala/system/public/js/tasks.js b/mirzaev/ebala/system/public/js/tasks.js index 1e3e03f..d31cd3b 100755 --- a/mirzaev/ebala/system/public/js/tasks.js +++ b/mirzaev/ebala/system/public/js/tasks.js @@ -48,118 +48,156 @@ if (typeof window.tasks !== "function") { // Кассиры получены // Блокировка полей ввода - for (let i = 1; i < cashiers.childElementCount; i += 2) + for (let i = 1; i < cashiers.childElementCount; i += 2) { if ( cashiers.children[i].children[0].children instanceof HTMLCollection - ) - for (const element of cashiers.children[i].children[0].children) + ) { + for (const element of cashiers.children[i].children[0].children) { element.setAttribute("disabled", true); - for (let i = 2; i < cashiers.childElementCount; i += 2) - if (cashiers.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < cashiers.childElementCount; i += 2) { + if (cashiers.children[i].children[0] instanceof HTMLElement) { cashiers.children[i].children[0].setAttribute("disabled", true); + } + } } if (displayers instanceof HTMLElement) { // Выкладчики получены // Блокировка полей ввода - for (let i = 1; i < displayers.childElementCount; i += 2) + for (let i = 1; i < displayers.childElementCount; i += 2) { if ( displayers.children[i].children[0].children instanceof HTMLCollection - ) - for (const element of displayers.children[i].children[0].children) + ) { + for (const element of displayers.children[i].children[0].children) { element.setAttribute("disabled", true); - for (let i = 2; i < displayers.childElementCount; i += 2) - if (displayers.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < displayers.childElementCount; i += 2) { + if (displayers.children[i].children[0] instanceof HTMLElement) { displayers.children[i].children[0].setAttribute("disabled", true); + } + } } if (gastronomes instanceof HTMLElement) { // Гастрономы получены // Блокировка полей ввода - for (let i = 1; i < gastronomes.childElementCount; i += 2) + for (let i = 1; i < gastronomes.childElementCount; i += 2) { if ( gastronomes.children[i].children[0].children instanceof HTMLCollection - ) - for (const element of gastronomes.children[i].children[0].children) + ) { + for (const element of gastronomes.children[i].children[0] + .children) { element.setAttribute("disabled", true); - for (let i = 2; i < gastronomes.childElementCount; i += 2) - if (gastronomes.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < gastronomes.childElementCount; i += 2) { + if (gastronomes.children[i].children[0] instanceof HTMLElement) { gastronomes.children[i].children[0].setAttribute("disabled", true); + } + } } if (brigadiers instanceof HTMLElement) { // Бригадиры получены // Блокировка полей ввода - for (let i = 1; i < brigadiers.childElementCount; i += 2) + for (let i = 1; i < brigadiers.childElementCount; i += 2) { if ( brigadiers.children[i].children[0].children instanceof HTMLCollection - ) - for (const element of brigadiers.children[i].children[0].children) + ) { + for (const element of brigadiers.children[i].children[0].children) { element.setAttribute("disabled", true); - for (let i = 2; i < brigadiers.childElementCount; i += 2) - if (brigadiers.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < brigadiers.childElementCount; i += 2) { + if (brigadiers.children[i].children[0] instanceof HTMLElement) { brigadiers.children[i].children[0].setAttribute("disabled", true); + } + } } if (loaders instanceof HTMLElement) { // Грузчики получены // Блокировка полей ввода - for (let i = 1; i < loaders.childElementCount; i += 2) + for (let i = 1; i < loaders.childElementCount; i += 2) { if ( loaders.children[i].children[0].children instanceof HTMLCollection - ) - for (const element of loaders.children[i].children[0].children) + ) { + for (const element of loaders.children[i].children[0].children) { element.setAttribute("disabled", true); - for (let i = 2; i < loaders.childElementCount; i += 2) - if (loaders.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < loaders.childElementCount; i += 2) { + if (loaders.children[i].children[0] instanceof HTMLElement) { loaders.children[i].children[0].setAttribute("disabled", true); + } + } } if (loaders_mobile instanceof HTMLElement) { // Мобильные грузчики получены // Блокировка полей ввода - for (let i = 1; i < loaders_mobile.childElementCount; i += 2) + for (let i = 1; i < loaders_mobile.childElementCount; i += 2) { if ( loaders_mobile.children[i].children[0].children instanceof HTMLCollection - ) + ) { for (const element of loaders_mobile.children[i].children[0] - .children) + .children) { element.setAttribute("disabled", true); - for (let i = 2; i < loaders_mobile.childElementCount; i += 2) - if (loaders_mobile.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < loaders_mobile.childElementCount; i += 2) { + if (loaders_mobile.children[i].children[0] instanceof HTMLElement) { loaders_mobile.children[i].children[0].setAttribute( "disabled", true ); + } + } } if (universals_mobile instanceof HTMLElement) { // Мобильные универсалы получены // Блокировка полей ввода - for (let i = 1; i < universals_mobile.childElementCount; i += 2) + for (let i = 1; i < universals_mobile.childElementCount; i += 2) { if ( universals_mobile.children[i].children[0].children instanceof HTMLCollection - ) + ) { for (const element of universals_mobile.children[i].children[0] - .children) + .children) { element.setAttribute("disabled", true); - for (let i = 2; i < universals_mobile.childElementCount; i += 2) - if (universals_mobile.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < universals_mobile.childElementCount; i += 2) { + if ( + universals_mobile.children[i].children[0] instanceof HTMLElement + ) { universals_mobile.children[i].children[0].setAttribute( "disabled", true ); + } + } } // Блокировка кнопки @@ -209,123 +247,162 @@ if (typeof window.tasks !== "function") { // Кассиры получены // Разблокировка полей ввода - for (let i = 1; i < cashiers.childElementCount; i += 2) + for (let i = 1; i < cashiers.childElementCount; i += 2) { if ( cashiers.children[i].children[0].children instanceof HTMLCollection - ) - for (const element of cashiers.children[i].children[0].children) + ) { + for (const element of cashiers.children[i].children[0] + .children) { element.removeAttribute("disabled"); - for (let i = 2; i < cashiers.childElementCount; i += 2) - if (cashiers.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < cashiers.childElementCount; i += 2) { + if (cashiers.children[i].children[0] instanceof HTMLElement) { cashiers.children[i].children[0].removeAttribute("disabled"); + } + } } if (displayers instanceof HTMLElement) { // Выкладчики получены // Разблокировка полей ввода - for (let i = 1; i < displayers.childElementCount; i += 2) + for (let i = 1; i < displayers.childElementCount; i += 2) { if ( displayers.children[i].children[0].children instanceof HTMLCollection - ) + ) { for (const element of displayers.children[i].children[0] - .children) + .children) { element.removeAttribute("disabled"); - for (let i = 2; i < displayers.childElementCount; i += 2) - if (displayers.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < displayers.childElementCount; i += 2) { + if (displayers.children[i].children[0] instanceof HTMLElement) { displayers.children[i].children[0].removeAttribute("disabled"); + } + } } if (gastronomes instanceof HTMLElement) { // Гастрономы получены // Разблокировка полей ввода - for (let i = 1; i < gastronomes.childElementCount; i += 2) + for (let i = 1; i < gastronomes.childElementCount; i += 2) { if ( gastronomes.children[i].children[0].children instanceof HTMLCollection - ) + ) { for (const element of gastronomes.children[i].children[0] - .children) + .children) { element.removeAttribute("disabled"); - for (let i = 2; i < gastronomes.childElementCount; i += 2) - if (gastronomes.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < gastronomes.childElementCount; i += 2) { + if (gastronomes.children[i].children[0] instanceof HTMLElement) { gastronomes.children[i].children[0].removeAttribute("disabled"); + } + } } if (brigadiers instanceof HTMLElement) { // Бригадиры получены // Разблокировка полей ввода - for (let i = 1; i < brigadiers.childElementCount; i += 2) + for (let i = 1; i < brigadiers.childElementCount; i += 2) { if ( brigadiers.children[i].children[0].children instanceof HTMLCollection - ) + ) { for (const element of brigadiers.children[i].children[0] - .children) + .children) { element.removeAttribute("disabled"); - for (let i = 2; i < brigadiers.childElementCount; i += 2) - if (brigadiers.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < brigadiers.childElementCount; i += 2) { + if (brigadiers.children[i].children[0] instanceof HTMLElement) { brigadiers.children[i].children[0].removeAttribute("disabled"); + } + } } if (loaders instanceof HTMLElement) { // Грузчики получены // Разблокировка полей ввода - for (let i = 1; i < loaders.childElementCount; i += 2) + for (let i = 1; i < loaders.childElementCount; i += 2) { if ( loaders.children[i].children[0].children instanceof HTMLCollection - ) - for (const element of loaders.children[i].children[0].children) + ) { + for (const element of loaders.children[i].children[0] + .children) { element.removeAttribute("disabled"); - for (let i = 2; i < loaders.childElementCount; i += 2) - if (loaders.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < loaders.childElementCount; i += 2) { + if (loaders.children[i].children[0] instanceof HTMLElement) { loaders.children[i].children[0].removeAttribute("disabled"); + } + } } if (loaders_mobile instanceof HTMLElement) { // Мобильные грузчики получены // Разблокировка полей ввода - for (let i = 1; i < loaders_mobile.childElementCount; i += 2) + for (let i = 1; i < loaders_mobile.childElementCount; i += 2) { if ( loaders_mobile.children[i].children[0].children instanceof HTMLCollection - ) + ) { for (const element of loaders_mobile.children[i].children[0] - .children) + .children) { element.removeAttribute("disabled"); - for (let i = 2; i < loaders_mobile.childElementCount; i += 2) - if (loaders_mobile.children[i].children[0] instanceof HTMLElement) + } + } + } + for (let i = 2; i < loaders_mobile.childElementCount; i += 2) { + if ( + loaders_mobile.children[i].children[0] instanceof HTMLElement + ) { loaders_mobile.children[i].children[0].removeAttribute( "disabled" ); + } + } } if (universals_mobile instanceof HTMLElement) { // Мобильные универсалы получены // Разблокировка полей ввода - for (let i = 1; i < universals_mobile.childElementCount; i += 2) + for (let i = 1; i < universals_mobile.childElementCount; i += 2) { if ( universals_mobile.children[i].children[0].children instanceof HTMLCollection - ) + ) { for (const element of universals_mobile.children[i].children[0] - .children) + .children) { element.removeAttribute("disabled"); - for (let i = 2; i < universals_mobile.childElementCount; i += 2) + } + } + } + for (let i = 2; i < universals_mobile.childElementCount; i += 2) { if ( universals_mobile.children[i].children[0] instanceof HTMLElement - ) + ) { universals_mobile.children[i].children[0].removeAttribute( "disabled" ); + } + } } // Разблокировка кнопки @@ -348,11 +425,11 @@ if (typeof window.tasks !== "function") { body.cashiers = []; // Запись в буфер JSON для отправки - for (let i = 1; i < cashiers.childElementCount; i += 2) + for (let i = 1; i < cashiers.childElementCount; i += 2) { if ( cashiers.children[i].children[0].children instanceof HTMLCollection - ) + ) { body.cashiers.push({ start: cashiers.children[i].children[0].children[0].value ?? null, @@ -362,6 +439,8 @@ if (typeof window.tasks !== "function") { 1000 ?? null, commentary: cashiers.children[i + 1].children[0].value ?? null, }); + } + } } if (displayers instanceof HTMLElement) { @@ -371,11 +450,11 @@ if (typeof window.tasks !== "function") { body.displayers = []; // Запись в буфер JSON для отправки - for (let i = 1; i < displayers.childElementCount; i += 2) + for (let i = 1; i < displayers.childElementCount; i += 2) { if ( displayers.children[i].children[0].children instanceof HTMLCollection - ) + ) { body.displayers.push({ start: displayers.children[i].children[0].children[0].value ?? null, @@ -387,6 +466,8 @@ if (typeof window.tasks !== "function") { commentary: displayers.children[i + 1].children[0].value ?? null, }); + } + } } if (gastronomes instanceof HTMLElement) { @@ -396,11 +477,11 @@ if (typeof window.tasks !== "function") { body.gastronomes = []; // Запись в буфер JSON для отправки - for (let i = 1; i < gastronomes.childElementCount; i += 2) + for (let i = 1; i < gastronomes.childElementCount; i += 2) { if ( gastronomes.children[i].children[0].children instanceof HTMLCollection - ) + ) { body.gastronomes.push({ start: gastronomes.children[i].children[0].children[0].value ?? null, @@ -412,6 +493,8 @@ if (typeof window.tasks !== "function") { commentary: gastronomes.children[i + 1].children[0].value ?? null, }); + } + } } if (brigadiers instanceof HTMLElement) { @@ -421,11 +504,11 @@ if (typeof window.tasks !== "function") { body.brigadiers = []; // Запись в буфер JSON для отправки - for (let i = 1; i < brigadiers.childElementCount; i += 2) + for (let i = 1; i < brigadiers.childElementCount; i += 2) { if ( brigadiers.children[i].children[0].children instanceof HTMLCollection - ) + ) { body.brigadiers.push({ start: brigadiers.children[i].children[0].children[0].value ?? null, @@ -437,6 +520,8 @@ if (typeof window.tasks !== "function") { commentary: brigadiers.children[i + 1].children[0].value ?? null, }); + } + } } if (loaders instanceof HTMLElement) { @@ -446,10 +531,10 @@ if (typeof window.tasks !== "function") { body.loaders = []; // Запись в буфер JSON для отправки - for (let i = 1; i < loaders.childElementCount; i += 2) + for (let i = 1; i < loaders.childElementCount; i += 2) { if ( loaders.children[i].children[0].children instanceof HTMLCollection - ) + ) { body.loaders.push({ start: loaders.children[i].children[0].children[0].value ?? null, @@ -457,6 +542,8 @@ if (typeof window.tasks !== "function") { date: loaders.children[i].children[0].children[2].value ?? null, commentary: loaders.children[i + 1].children[0].value ?? null, }); + } + } } if (loaders_mobile instanceof HTMLElement) { @@ -466,11 +553,11 @@ if (typeof window.tasks !== "function") { body.loaders_mobile = []; // Запись в буфер JSON для отправки - for (let i = 1; i < loaders_mobile.childElementCount; i += 2) + for (let i = 1; i < loaders_mobile.childElementCount; i += 2) { if ( loaders_mobile.children[i].children[0].children instanceof HTMLCollection - ) + ) { body.loaders_mobile.push({ start: loaders_mobile.children[i].children[0].children[0].value ?? @@ -484,6 +571,8 @@ if (typeof window.tasks !== "function") { commentary: loaders_mobile.children[i + 1].children[0].value ?? null, }); + } + } } if (universals_mobile instanceof HTMLElement) { @@ -493,11 +582,11 @@ if (typeof window.tasks !== "function") { body.universals_mobile = []; // Запись в буфер JSON для отправки - for (let i = 1; i < universals_mobile.childElementCount; i += 2) + for (let i = 1; i < universals_mobile.childElementCount; i += 2) { if ( universals_mobile.children[i].children[0].children instanceof HTMLCollection - ) + ) { body.universals_mobile.push({ start: universals_mobile.children[i].children[0].children[0].value ?? @@ -511,6 +600,8 @@ if (typeof window.tasks !== "function") { commentary: universals_mobile.children[i + 1].children[0].value ?? null, }); + } + } } // Запрос к серверу @@ -1746,47 +1837,60 @@ if (typeof window.tasks !== "function") { * @param {string} name Название * @param {string|number|null} value Значение * @param {HTMLElement|null} button Кнопка + * @param {bool} force Принудительное выполнение (используется в damper()) * * @return {void} */ - static filter = damper(async (name, value, button) => { - if (typeof name === "string") { - // Получено название + static filter = damper( + async (name, value, button, force = false) => { + return new Promise(async (resolve, reject) => { + if (typeof name === "string") { + // Получено название - // Инициализация сериализованного пути к директории - const path = `tasks_filter_${name}`; + // Инициализация сериализованного пути к директории + const path = `tasks_filter_${name}`; - if (typeof value === "string" || typeof value === "number") { - // Получено значение + if (typeof value === "string" || typeof value === "number") { + // Получено значение - // Запись нового значения - buffer.write(path, value); - } else { - // Не получено значение + // Запись нового значения + await buffer.write(path, value); - // Чтение текущего значения - value = +(await buffer.read(path)); + resolve(); + } else { + // Не получено значение - // Инициализация значения по умолчанию - if (isNaN(value)) value = 0; + // Чтение текущего значения + value = +(await buffer.read(path)); - // Запись нового значения (инвертирование) - buffer.write(path, ++value < 3 ? value : 0); + // Инициализация значения по умолчанию + if (isNaN(value)) value = 0; - if (button instanceof HTMLElement) { - // Получена кнопка + // Запись нового значения (инвертирование) + await buffer.write(path, ++value < 3 ? value : 0); - // Деинициализация классов статуса фильтра - button.classList.remove("earth", "sand", "river"); + resolve(); - // Инициализация классов статуса фильтра - if (value === 1) button.classList.add("sand"); - else if (value === 2) button.classList.add("river"); - else button.classList.add("earth"); + if (button instanceof HTMLElement) { + // Получена кнопка + + // Деинициализация классов статуса фильтра + button.classList.remove("earth", "sand", "river"); + + // Инициализация классов статуса фильтра + if (value === 1) button.classList.add("sand"); + else if (value === 2) button.classList.add("river"); + else button.classList.add("earth"); + } + } } - } - } - }, 300); + + reject(); + }); + }, + 300, + 3 + ); /** * Записать фильтр поиска diff --git a/mirzaev/ebala/system/views/elements/tasks.html b/mirzaev/ebala/system/views/elements/tasks.html index 2070e0d..fda7462 100644 --- a/mirzaev/ebala/system/views/elements/tasks.html +++ b/mirzaev/ebala/system/views/elements/tasks.html @@ -17,7 +17,7 @@ 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 %} - {{ row.task.work + {{ row.task.work }} {{ row.task.generated.start }} diff --git a/mirzaev/ebala/system/views/pages/tasks.html b/mirzaev/ebala/system/views/pages/tasks.html index 4a84ad5..d0ec569 100644 --- a/mirzaev/ebala/system/views/pages/tasks.html +++ b/mirzaev/ebala/system/views/pages/tasks.html @@ -21,7 +21,8 @@ {% endif %} {% if account.type == 'administrator' or account.type == 'operator' %} - + + {% endif %} @@ -89,7 +90,7 @@ Дата ФИО - Работа + Работа @@ -145,4 +146,5 @@ + {% endblock %}