<?php

declare(strict_types=1);

namespace mirzaev\ebala\controllers;

// Файлы проекта
use mirzaev\ebala\controllers\core,
	mirzaev\ebala\controllers\traits\errors,
	mirzaev\ebala\models\task as model,
	mirzaev\ebala\models\account,
	mirzaev\ebala\models\worker,
	mirzaev\ebala\models\market,
	mirzaev\ebala\models\payments,
	mirzaev\ebala\models\core as _core;

// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;

// System libraries
use datetime,
	datetimezone,
	exception;

/**
 * Контроллер задания
 *
 * @package mirzaev\ebala\controllers
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class task extends core
{
	use errors;

	/**
	 * Типы работ
	 */
	final public const WORKS = ['Кассир', 'Выкладчик', 'Гастроном', 'Бригадир', 'Грузчик', 'Мобильный грузчик', 'Мобильный универсал'];

	/**
	 * Создать
	 *
	 * @param array $parameters Параметры запроса (json в php://input)
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function create(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора или магазина

				// Инициализация буфера ошибок
				$this->errors['tasks'] ??= [];

				if (!empty($json = json_decode(file_get_contents('php://input'), true, 4))) {
					// Найден и декодирован json-документ с данными для создания заявок

					foreach ($json as $work => $tasks) {
						// Перебор категорий (колонок)

						foreach ($tasks as $task) {
							// Перебор заявок

							// Создание заявки
							model::create(
								work: model::label($work),
								market: $this->account->type === 'market' ? account::market($this->account->getId())?->id : null,
								start: $task['start'],
								end: $task['end'],
								date: $task['date'],
								commentary: $task['commentary'],
								errors: $this->errors['tasks']
							);
						}
					}

					// Запись заголовков ответа
					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();
				} else throw new exception('Не удалось инициализировать JSON-документ с данными заявок');
			} 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();
		}
	}

	/**
	 * Прочитать
	 *
	 * @param array $parameters Параметры запроса
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function read(array $parameters = []): void
	{
		try {
			if ($this->account->status()) {
				// Авторизован аккаунт

				// Инициализация буфера AQL-выражения для инъекции фильтра по интервалу
				$interval = '';

				foreach (['from', 'to'] as $name) {
					// Перебор фильтров временного промежутка (И)

					// Инициализация значения (приоритет у cookie)
					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";
					else if ($name === 'to') $interval .= " && task.date <= $value";
				}

				// Очистка от бинарных операторов сравнения с только одним операндом (крайние)
				$interval = trim(trim(trim($interval), '&&'));

				// Инициализация буфера AQL-выражения для инъекции фильтра по интервалу
				$polysemantic = '';

				foreach (['rating'] as $name) {
					// Перебор фильтров с произвольными значениями (И)

					// Инициализация значения (приоритет у cookie) 
					$value = $_COOKIE["tasks_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? null;

					// Найдено значение?
					if ($value === null) continue;

					// Генерация AQL-выражения для инъекции в строку запроса
					if ($name === 'rating' && $value > 0) $polysemantic .= " && task.rating >= $value";
				}

				// Очистка от бинарных операторов сравнения с только одним операндом (крайние)
				$polysemantic = trim(trim(trim($polysemantic), '&&'));

				// Инициализация буферов AQL-выражений для инъекции фильтра по статусам
				$statuses_and = '';
				$statuses_or = '';

				foreach (['confirmed', 'waiting', 'published', 'unpublished', 'problematic', 'hided', 'completed'] as $name) {
					// Перебор фильтров по статусам

					// Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue)
					if (empty($value = $_COOKIE["tasks_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? 0)) continue;

					// Конвертация ярлыков
					$converted = match ($name) {
						'waiting' => 'confirmed',
						'unpublished' => 'published',
						default => $name
					};

					// Генерация выражения
					$expression = "task.$converted == " . ($name === 'unpublished' || $name === 'waiting' ? 'false' : 'true');

					// Генерация AQL-выражения для инъекции в строку запроса
					if ($value === '1') $statuses_and .= " && " . $expression;
					else if ($value === '2') $statuses_or .= " || " . $expression;
				}

				// Очистка от бинарных операторов сравнения с только одним операндом (крайние)
				$statuses_and = trim(trim(trim($statuses_and), '&&'));
				$statuses_or = trim(trim(trim($statuses_or), '||'));

				// Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ"
				$statuses_merged = (empty($statuses_and) ? '' : "($statuses_and)") . (empty($statuses_or) ? '' : (empty($statuses_and) ? '' : ' || ') . "($statuses_or)");

				// Инициализация общего буфера с AQL-выражениями
				$filters = '';

				// Объединение фильров в единую строку с AQL-выражениями для инъекции
				if (!empty($interval)) $filters .= $interval;
				if (!empty($statuses_merged)) $filters .= empty($filters) ? $statuses_merged : " && ($statuses_merged)";
				if (!empty($polysemantic)) $filters .= empty($filters) ? $polysemantic : " && $polysemantic";

				if (isset($parameters['row'])) {
					// Запрошена строка

					// Добавление идентификатора строки в фильтр
					$filters .= ' && task._key == "' . $parameters['row'] . '"';

					// Инициализация данных для генерации HTML-документа с таблицей
					if ($_SERVER['INTERFACE'] === 'worker')
						$this->view->rows = model::list(before: 'FILTER task.worker == "' . account::worker($this->account->getId())?->id . '"' . " && ($filters)");
					else if ($_SERVER['INTERFACE'] === 'operator')
						$this->view->rows = model::list(before: "FILTER ($filters)");
					else if ($_SERVER['INTERFACE'] === 'market')
						$this->view->rows = model::list(before: 'FILTER task.market == "' . account::market($this->account->getId())?->id . '"' . " && ($filters)");
					else if ($_SERVER['INTERFACE'] === 'administrator')
						$this->view->rows = model::list(before: "FILTER ($filters)");
					else $this->view->rows = [];
				} else {
					// Запрошена страница (множество строк)

					// Реинициализация номера актуальной страницы
					if (isset($parameters['page'])) $this->session->write(['tasks' => ['page' => $parameters['page']]]);
					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'] ?? '';
					if (mb_strlen($search) < 3) $search = null;
					$search_query = empty($search)
						? null
						: <<<AQL
						SEARCH 
							task.commentary IN TOKENS(@search, 'text_ru') 
							|| STARTS_WITH(task._key, @search)
							|| STARTS_WITH(task.worker, @search)
							|| STARTS_WITH(task.market, @search)
							|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(task._key, TOKENS(@search, 'text_en')[0], 2, true))
							|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(task.worker, TOKENS(@search, 'text_en')[0], 2, true))
							|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(task.market, TOKENS(@search, 'text_en')[0], 2, true))
					AQL;

					// Инициализация данных для генерации HTML-документа с таблицей
					if ($_SERVER['INTERFACE'] === 'worker') {
						// Сотрудник

						$this->view->rows = model::list(
							before: sprintf(
								<<<AQL
								%s
								FILTER task.deleted != true && task.worker == @worker%s
							AQL,
								$search_query,
								empty($filters) ? null : " && ($filters)"
							),
							page: (int) $this->session->buffer['worker']['tasks']['page'],
							target: empty($search) ? model::COLLECTION : 'registry_tasks',
							binds: ['worker' => account::worker($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search])
						);
					} else if ($_SERVER['INTERFACE'] === 'market') {
						// Магазин

						$this->view->rows = model::list(
							before: sprintf(
								<<<AQL
								%s
								FILTER task.deleted != true && task.market == @market%s
							AQL,
								$search_query,
								empty($filters) ? null : " && ($filters)"
							),
							page: (int) $this->session->buffer['market']['tasks']['page'],
							target: empty($search) ? model::COLLECTION : 'registry_tasks',
							binds: ['market' => account::market($this->account->getId())?->id] + (empty($search) ? [] : ['search' => $search])
						);
					} else if ($_SERVER['INTERFACE'] === 'operator') {
						// Оператор

						$this->view->rows = model::list(
							before: sprintf(
								<<<AQL
								%s
								FILTER task.deleted != true%s
							AQL,
								$search_query,
								empty($filters) ? null : " && ($filters)"
							),
							page: (int) $this->session->buffer['operator']['tasks']['page'],
							target: empty($search) ? model::COLLECTION : 'registry_tasks',
							binds: empty($search) ? [] : [
								'search' => $search
							]
						);
					} else if ($_SERVER['INTERFACE'] === 'administrator') {
						// Администратор

						$this->view->rows = model::list(
							before: sprintf(
								<<<AQL
								%s
								FILTER task.deleted != true%s
							AQL,
								$search_query,
								empty($filters) ? null : " && ($filters)"
							),
							page: (int) $this->session->buffer['administrator']['tasks']['page'],
							target: empty($search) ? model::COLLECTION : 'registry_tasks',
							binds: empty($search) ? [] : [
								'search' => $search
							]
						);
					} else $this->view->rows = [];

					// Запись в cookie	
					setcookie(
						'tasks_page',
						(string) $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'],
						[
							'expires' => strtotime('+1 hour'),
							'path' => '/',
							'secure' => true,
							'httponly' => false,
							'samesite' => 'strict'
						]
					);

					// Запись в глобальную переменную шаблонизатора обрабатываемой страницы
					$this->view->page = $parameters['page'];
				};

				// Предобработка строк перед генерацией документа
				$this->view->rows = static::preprocessing($this->account, $this->view->rows);

				// Запись заголовков ответа
				header('Content-Type: application/json');
				header('Content-Encoding: none');
				header('X-Accel-Buffering: no');

				// Инициализация буфера вывода
				ob_start();

				// Генерация ответа
				echo json_encode(
					[
						'rows' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
						'errors' => self::parse_only_text($this->errors)
					]
				);

				// Запись заголовков ответа
				header('Content-Length: ' . ob_get_length());

				// Отправка и деинициализация буфера вывода
				ob_end_flush();
				flush();
			} 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();
		}
	}

	/** 
	 * Предобработка строк перед генерацией документа
	 *
	 * @param account $account Аккаунт
	 * @param array $rows Строки
	 *
	 * @return array Обработанные строки
	 *
	 * @todo Переделать в model::hours($from, $to);
	 */
	protected static function preprocessing(account $account, array $rows): array
	{
		// Инициализация буфера прочитанных из базы данных строк
		$buffer = $rows;

		foreach ($rows as $number => $row) {
			// Перебор прочитанных из базы данных строк

			// Инициализация ярлыка
			$link = &$buffer[$number];

			if (!empty($row->task['start'])) {
				// Найдено начало

				// Инициализация даты
				$date = (new DateTime('@' . $row->task['date']))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

				// Инициализация времени
				$start = datetime::createFromFormat('H:i', (string) $row->task['start']);

				// Перенос времени в дату
				$start = $date->setTime((int) $start->format('H'), (int) $start->format('i'));


				// Запись в буфер
				$link->task = ['start' => $start] + $link->task;
			}

			if (!empty($row->task['end'])) {
				// Найден конец

				// Инициализация даты
				$date = (new DateTime('@' . $row->task['date']))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

				// Инициализация времени
				$end = datetime::createFromFormat('H:i', (string) $row->task['end']);

				// Перенос времени в дату
				$end = $date->setTime((int) $end->format('H'), (int) $end->format('i'));

				// Запись в буфер
				$link->task = ['end' => $end] + $link->task;
			}

			// Инициализация буфера сгенерированных данных работы для шаблонизатора
			$generated = [];

			// Генерация данных работы для шаблонизатора
			if (
				$link->task['start'] instanceof datetime
				&& $link->task['end'] instanceof datetime
			) {
				$generated['hours'] = (float) $link->task['start']->diff($link->task['end'])->format('%R%H.%i');
				if ($generated['hours'] < 0) $generated['hours'] += 24;
				if ($generated['hours'] >= 6.5 && $generated['hours'] < 9) $generated['hours'] -= 0.5;
				else if ($generated['hours'] >= 9 && $generated['hours'] < 12.5) $generated['hours'] -= 1;
				else if ($generated['hours'] >= 12.5) $generated['hours'] -= 1.5;
			}
			if ($link->task['start'] instanceof datetime) $generated['start'] = $link->task['start']->format('H:i');
			if ($link->task['end'] instanceof datetime) $generated['end'] = $link->task['end']->format('H:i');

			// Инициализация счётчика непрочитанных сообщений
			$generated['chat'] = [
				'unreaded' => 0
			];

			if ($account->type === 'worker') {
				// Сотрудник

				foreach ($link->task['chats']['worker'] ?? [] as $message) {
					// Перебор сообщений из чата: СОТРУДНИК <-> ОПЕРАТОР

					// Подсчёт непрочитанных сообщений
					if (!array_key_exists((string) $account->getKey(), $message['readed'] ?? [])) ++$generated['chat']['unreaded'];
				}
			} else if ($account->type === 'market') {
				// Магазин

				foreach ($link->task['chats']['market'] ?? [] as $message) {
					// Перебор сообщений из чата: МАГАЗИН <-> ОПЕРАТОР

					// Подсчёт непрочитанных сообщений
					if (!array_key_exists((string) $account->getKey(), $message['readed'] ?? [])) ++$generated['chat']['unreaded'];
				}
			} else if ($account->type === 'operator' || $account->type === 'administrator') {
				// Оператор или администратор

				foreach ($link->task['chats'] ?? [] as $chat)
					foreach ($chat as $message) {
						// Перебор сообщений из всех чатов

						// Подсчёт непрочитанных сообщений
						if (!array_key_exists((string) $account->getKey(), $message['readed'] ?? [])) ++$generated['chat']['unreaded'];
					}
			}

			// Запись из буфера сгенерированных данных работы для шаблонизатора в буфер переменных окружения шаблонизатора
			$link->task = ['generated' => $generated] + $link->task;
		}

		// Трансфер строк из модифицированного буфера процесса в буфер вывода
		return $buffer;
	}

	/**
	 * Заменить сотрудника или магазин
	 *
	 * @param array $parameters Параметры запроса
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function update(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора или магазина

				if (($task = model::read('d._key == "' . $parameters['task'] . '"', amount: 1)) instanceof _document) {
					// Найдена заявка

					// Заявка не принадлежит запросившему магазину?
					if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
						throw new exception('Вы не авторизованы для редактирования этой заявки');

					// Заявка подтверждена?
					if ($task->confirmed) throw new exception('Запрещено редактировать подтверждённую заявку');

					if ($this->account->type === 'market') {
						// Магазин

						// Инициализация даты
						$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

						// Инициализация времени
						$start = datetime::createFromFormat('H:i', (string) $task->start);
						$end = datetime::createFromFormat('H:i', (string) $task->end);

						// Перенос времени в дату
						$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 (time() - $start > 0)
							throw new exception('Запрещено редактировать начатую заявку');

						// Заявка уже прошла?
						if (time() - $end > 0)
							throw new exception('Запрещено редактировать прошедшую заявку');

						// Заявка уже завершена?
						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 часов до её начала');
					}

					// Запись в реестре последних обновивших
					$task->updates = [$this->account->type => match ($this->account->type) {
						'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
						default => $this->account->getKey()
					}] + ($task->updates ?? []);

					if (!empty($parameters['worker'])) {
						// Передан сотрудник

						if ($parameters['worker'] === 'delete') {
							// Удалить сотрудника

							// Удаление сотрудника
							$task->worker = null;

							if (_core::update($task)) {
								// Записано изменение в базу данных

								// Инициализация строки в глобальную переменную шаблонизатора
								$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

								// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
								$this->view->page = null;

								// Запись заголовков ответа
								header('Content-Type: application/json');
								header('Content-Encoding: none');
								header('X-Accel-Buffering: no');

								// Инициализация буфера вывода
								ob_start();

								// Генерация ответа
								echo json_encode(
									[
										'updated' => true,
										'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
										'errors' => self::parse_only_text($this->errors)
									]
								);

								// Запись заголовков ответа
								header('Content-Length: ' . ob_get_length());

								// Отправка и деинициализация буфера вывода
								ob_end_flush();
								flush();
							} else throw new exception('Не удалось записать изменения в базу данных');
						} else {
							// Записать нового сотрудника

							if (($worker = worker::read('d.id == "' . $parameters['worker'] . '" && d.active == true', amount: 1)) instanceof _document) {
								// Найден сотрудник (запрашиваемый для записи сотрудник существует в базе данных)

								if ($task->worker !== $parameters['worker']) {
									// Идентификатор запрашиваемого сотрудника не равен актуальному

									// Запись сотрудника
									$task->worker = $worker->id;

									// Снятие с публикации
									$task->published = false;

									if (_core::update($task)) {
										// Записано изменение в базу данных

										// Инициализация строки в глобальную переменную шаблонизатора
										$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

										// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
										$this->view->page = null;

										// Запись заголовков ответа
										header('Content-Type: application/json');
										header('Content-Encoding: none');
										header('X-Accel-Buffering: no');

										// Инициализация буфера вывода
										ob_start();

										// Генерация ответа
										echo json_encode(
											[
												'updated' => true,
												'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
												'errors' => self::parse_only_text($this->errors)
											]
										);

										// Запись заголовков ответа
										header('Content-Length: ' . ob_get_length());

										// Отправка и деинициализация буфера вывода
										ob_end_flush();
										flush();
									} else throw new exception('Не удалось записать изменения в базу данных');
								} else throw new exception('Сотрудник уже назначен');
							} else throw new exception('Не найден сотрудник');
						}
					} else if (!empty($parameters['market'])) {
						// Передан магазин

						// Магазин пытается перезаписать магазин?
						if ($this->account->type === 'market') throw new exception('Вы не авторизованы переназначать магазин заявки');

						if ($parameters['market'] === 'delete') {
							// Удалить магазин

							// Удаление магазина
							$task->market = null;

							if (_core::update($task)) {
								// Записано изменение в базу данных

								// Инициализация строки в глобальную переменную шаблонизатора
								$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

								// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
								$this->view->page = null;

								// Запись заголовков ответа
								header('Content-Type: application/json');
								header('Content-Encoding: none');
								header('X-Accel-Buffering: no');

								// Инициализация буфера вывода
								ob_start();

								// Генерация ответа
								echo json_encode(
									[
										'updated' => true,
										'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
										'errors' => self::parse_only_text($this->errors)
									]
								);

								// Запись заголовков ответа
								header('Content-Length: ' . ob_get_length());

								// Отправка и деинициализация буфера вывода
								ob_end_flush();
								flush();
							} else throw new exception('Не удалось записать изменения в базу данных');
						} else {
							// Записать новый магазин

							if (($market = market::read('d.id == "' . $parameters['market'] . '" && d.active == true', amount: 1)) instanceof _document) {
								// Найден магазин (запрашиваемый для записи магазин существует в базе данных)

								if ($task->market !== $parameters['market']) {
									// Идентификатор запрашиваемого сотрудника не равен актуальному

									// Запись магазина
									$task->market = $market->id;

									if (_core::update($task)) {
										// Записано изменение в базу данных

										// Инициализация строки в глобальную переменную шаблонизатора
										$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

										// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
										$this->view->page = null;

										// Запись заголовков ответа
										header('Content-Type: application/json');
										header('Content-Encoding: none');
										header('X-Accel-Buffering: no');

										// Инициализация буфера вывода
										ob_start();

										// Генерация ответа
										echo json_encode(
											[
												'updated' => true,
												'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
												'errors' => self::parse_only_text($this->errors)
											]
										);

										// Запись заголовков ответа
										header('Content-Length: ' . ob_get_length());

										// Отправка и деинициализация буфера вывода
										ob_end_flush();
										flush();
									} else throw new exception('Не удалось записать изменения в базу данных');
								} else throw new exception('Магазин уже назначен');
							} else throw new exception('Не найден магазин');
						}
					} else throw new exception('Не получены данные для записи');
				} else throw new exception('Не найдена заявка');
			} else throw new exception('Вы не авторизованы');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];

			// Запись заголовков ответа
			header('Content-Type: application/json');
			header('Content-Encoding: none');
			header('X-Accel-Buffering: no');

			// Инициализация буфера вывода
			ob_start();

			// Генерация ответа
			echo json_encode(
				[
					'updated' => false,
					'errors' => self::parse_only_text($this->errors)
				]
			);

			// Запись заголовков ответа
			header('Content-Length: ' . ob_get_length());

			// Отправка и деинициализация буфера вывода
			ob_end_flush();
			flush();
		}
	}

	/**
	 * Прочитать данные задачи
	 *
	 * @param array $parameters Параметры запроса
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function task(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора или магазина

				// Инициализация данных сотрудника
				$this->view->task = model::read(
					'd._key == "' . $parameters['task'] . '"',
					return: $this->account->type === 'market'
						? '{_key: d._key, created: d.created, updated: d.updated, date: d.date, start: d.start, end: d.end, market: d.market, confirmed: d.confirmed, completed: d.completed }'
						: '{_key: d._key, created: d.created, updated: d.updated, date: d.date, start: d.start, end: d.end, market: d.market, confirmed: d.confirmed, completed: d.completed, hided: d.hided, updates: d.updates }'
				)->getAll();

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $this->view->task['market'] !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для чтения этой заявки');

				// Удаление данных из выдачи
				$this->view->task = ['market' => null] + $this->view->task;

				if (!empty($this->view->task)) {
					// Найдены данные задачи

					// Инициализация даты
					$date = (new DateTime('@' . $this->view->task['date']))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

					// Инициализация времени
					$start = datetime::createFromFormat('H:i', (string) $this->view->task['start']);
					$end = datetime::createFromFormat('H:i', (string) $this->view->task['end']);

					// Перенос времени в дату
					$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');

					// Деинициализация неактуальных параметров
					$this->view->task = ['date' => null, 'start' => null, 'end' => null] + $this->view->task;

					// Заявка уже началась? (в том числе может и завершена)
					$passed = (new DateTime())->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'))->format('U') - $start >= 0;

					// Инициализация буфера данных задачи
					$buffer = [];

					// Перевод ключей на русский язык
					foreach ($this->view->task as $key => $value)
						if ($key === 'updates')
							foreach ($value as $key => $value) $buffer['updates'][$key] = [
								'label' => match ($key) {
									'operator' => 'Оператор',
									'market' => 'Магазин',
									'administrator' => 'Администратор',
									'worker' => 'Сотрудник',
									default => $key
								},
								'value' => $value
							];
						else if (match ($key) {
							'created', 'updated', 'confirmed', 'hided', 'completed', '_key' => true,
							'start', 'end' => $passed, // Только для завершённой заявки
							default => false
						}) $buffer[$key] = [
							'label' => match ($key) {
								'created' => 'Создано',
								'updated' => 'Обновлено',
								'confirmed' => 'Подтверждено',
								'hided' => 'Скрыто',
								'completed' => 'Завершено',
								'start' => 'Начало',
								'end' => 'Конец',
								default => $key
							},
							'value' => $value
						];

					if ($passed) {
						// Начатая заявка (в том числе завершённая)

						// Конвертация времени в дату
						$buffer['start']['value'] = $start;
						$buffer['end']['value'] = $end;
					}

					// Запись из буфера данных задачи
					$this->view->task = $buffer;

					// Запись заголовков ответа
					header('Content-Type: application/json');
					header('Content-Encoding: none');
					header('X-Accel-Buffering: no');

					// Инициализация буфера вывода
					ob_start();

					// Генерация ответа
					echo json_encode([
						'start' => $start,
						'end' => $end,
						'task' => $this->view->render(
							DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'task' . DIRECTORY_SEPARATOR . ($passed ? 'passed.html' : 'actual.html')
						),
						'errors' => self::parse_only_text($this->errors)
					]);

					// Запись заголовков ответа
					header('Content-Length: ' . ob_get_length());

					// Отправка и деинициализация буфера вывода
					ob_end_flush();
					flush();
				} else throw new exception('Не найдена заявка');
			} else throw new exception('Вы не авторизованы');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$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();
		}
	}

	/**
	 * Прочитать данные сотрудника
	 *
	 * @param array $parameters Параметры запроса
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function worker(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора или оператора

				// Инициализация данных сотрудника
				$this->view->worker = worker::read(
					'd.id == "' . $parameters['worker'] . '"',
					return: $this->account->type === 'market'
						? '{id: d.id, created: d.created, updated: d.updated, name: d.name, number: d.number, mail: d.mail, birth: d.birth, rating: d.rating}'
						: 'd'
				)->getAll();

				if (!empty($this->view->worker)) {
					// Найдены данные сотрудника

					// Инициализация буфера данных сотрудника
					$buffer = [];

					// Перевод ключей на русский язык
					foreach ($this->view->worker as $key => $value) $buffer[$key] = [
						'label' => match ($key) {
							'created' => 'Создано',
							'updated' => 'Обновлено',
							'name' => 'ФИО',
							'number' => 'Номер',
							'mail' => 'Почта',
							'birth' => 'Дата рождения',
							'address' => 'Адрес',
							'rating' => 'Рейтинг',
							'passport' => 'Паспорт',
							'issued' => 'Место выдачи',
							'hiring' => 'Дата найма',
							'district' => 'Адрес регистрации',
							'department' => 'Отделение',
							'requisites' => 'Реквизиты',
							'fired' => 'Уволен',
							'tax' => 'ИНН',
							'city' => 'Город',
							'payment' => 'Форма оплаты',
							'works' => 'Формы работ',
							default => $key
						},
						'value' => $value
					];

					// Удаление ненужных параметров из выдачи
					foreach ($buffer as $key => $value) if (match ($key) {
						'type', 'active' => true,
						default => false
					}) unset($buffer[$key]);

					// Запись из буфера данных
					$this->view->worker = $buffer;

					// Возврат (успех)
					// Запись заголовков ответа
					header('Content-Type: application/json');
					header('Content-Encoding: none');
					header('X-Accel-Buffering: no');

					// Инициализация буфера вывода
					ob_start();

					// Генерация ответа
					echo json_encode(
						[
							'worker' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'worker.html'),
							'errors' => self::parse_only_text($this->errors)
						]
					);

					// Запись заголовков ответа
					header('Content-Length: ' . ob_get_length());

					// Отправка и деинициализация буфера вывода
					ob_end_flush();
					flush();
				} else throw new exception('Не найдена заявка');
			} else throw new exception('Вы не авторизованы');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$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();
		}
	}

	/**
	 * Прочитать данные магазина
	 *
	 * @param array $parameters Параметры запроса
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function market(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
				// Авторизован аккаунт администратора или оператора

				// Инициализация данных
				$this->view->market = market::read('d.id == "' . $parameters['market'] . '"')?->getAll();

				if (!empty($this->view->market)) {
					// Найдены данные магазина

					// Инициализация буфера данных
					$buffer = [];

					// Перевод ключей на русский язык
					foreach ($this->view->market as $key => $value) $buffer[$key] = [
						'label' => match ($key) {
							'created' => 'Создано',
							'updated' => 'Обновлено',
							'name' => 'ФИО',
							'type' => 'Тип',
							'director' => 'Директор',
							'address' => 'Адрес',
							'city' => 'Город',
							'number' => 'Номер',
							'mail' => 'Почта',
							'commentary' => 'Комментарий',
							default => $key
						},
						'value' => $value
					];

					// Удаление ненужных параметров из выдачи
					foreach ($buffer as $key => $value) if (match ($key) {
						'active' => true,
						default => false
					}) unset($buffer[$key]);

					// Запись из буфера данных
					$this->view->market = $buffer;

					// Запись заголовков ответа
					header('Content-Type: application/json');
					header('Content-Encoding: none');
					header('X-Accel-Buffering: no');

					// Инициализация буфера вывода
					ob_start();

					// Генерация ответа
					echo json_encode(
						[
							'market' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'market.html'),
							'errors' => self::parse_only_text($this->errors)
						]
					);

					// Запись заголовков ответа
					header('Content-Length: ' . ob_get_length());

					// Отправка и деинициализация буфера вывода
					ob_end_flush();
					flush();
				} else throw new exception('Не найдена заявка');
			} else throw new exception('Вы не авторизованы');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$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();
		}
	}

	/**
	 * Подтвердить (отклонить)
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function confirm(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
				// Авторизован аккаунт администратора или оператора

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				if ($task instanceof _document) {
					// Найдена заявка

					// Изменение статуса подтверждения
					$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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано изменение в базу данных

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
						$this->view->page = null;

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'confirmed' => $this->view->rows[0]->task['confirmed'],
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Добавить статус проблемы (убрать статус проблемы)
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function problem(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора и магазина

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для заявления о проблеме с этой заявкой');

				// Инициализация даты
				$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

				// Инициализация времени
				$start = datetime::createFromFormat('H:i', (string) $task->start);
				$end = datetime::createFromFormat('H:i', (string) $task->end);

				// Перенос времени в дату
				$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 ($task instanceof _document) {
					// Найдена заявка

					if ($task->problematic) {
						// Проблемная заявка (запрошена отмена статуса проблемной заявки)

						// Изменение статуса наличия проблемы
						$task->problematic = false;
					} else {
						// Не проблемная заявка (запрошена установка статуса проблемной заявки)

						if (empty($parameters['text'])) throw new exception('Необходимо передать текст с объяснением проблемы');
						else {
							// Получен текст с описанием проблемы

							// Изменение статуса наличия проблемы
							$task->problematic = true;
						}
					}

					// Запись в реестре последних обновивших
					$task->updates = [$this->account->type => match ($this->account->type) {
						'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
						default => $this->account->getKey()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано изменение в базу данных

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
						$this->view->page = null;

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'problematic' => $this->view->rows[0]->task['problematic'],
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Завершить
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function complete(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора и магазина

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для завершения этой заявки');

				// Заявка завершена?
				if ($task->completed) throw new exception('Заявка уже завершена');

				// Инициализация даты
				$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

				// Инициализация времени
				$start = datetime::createFromFormat('H:i', (string) $task->start);
				$end = datetime::createFromFormat('H:i', (string) $task->end);

				// Перенос времени в дату
				$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 time() - $start < 0)
					throw new exception('Запрещено завершать не начатую заявку');

				if ($task instanceof _document) {
					// Найдена заявка

					if (empty($parameters['rating'])) throw new exception('Необходимо передать оценку');
					else if (($parameters['rating'] = (int) $parameters['rating']) < 1) throw new exception('Оценка должна быть не менее 1');
					else if ($parameters['rating'] > 5) throw new exception('Оценка должна быть не более 5');
					else {
						// Получена оценка

						// Запись оценки
						$task->rating = $parameters['rating'];

						if (!empty($parameters['review'])) {
							// Получен отзыв

							// Запись отзыва
							$task->review = $parameters['review'];
						}

						// Запись статуса завершения
						$task->completed = true;

						// Снятие с публикации
						$task->published = false;

						// Иниализация сотрудника
						$worker = worker::read('d.id == "' . $task->worker . '"');

						// Инициализация магазина
						$market = market::read('d.id == "' . $task->market . '"');

						// Подсчёт часов работы
						$hours = model::hours($task->start, $task->end, $this->errors);

						// Инициализация цены работы за 1 час
						$hour = payments::hour($market->city, $task->work);

						// Подсчёт оплаты за работу
						$payment = $hour * $hours;

						// Инициализация штрафа
						$penalty = payments::penalty($task->rating ?? null);

						// Инициализация премии
						$bonus = payments::bonus($task->rating ?? null);

						// Инициализация транзакции к оплате сотруднику
						model::transaction(
							$task->getId(),
							$worker->getId(),
							$payment - ($penalty === null ? $payment : -$penalty) + $bonus,
							$this->errors
						);
					}

					// Запись в реcстре последних обновивших
					$task->updates = [$this->account->type => match ($this->account->type) {
						'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
						default => $this->account->getKey()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано изменение в базу данных

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
						$this->view->page = null;

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'completed' => $this->view->rows[0]->task['completed'],
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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(
				[
					'completed' => $task->completed ?? false,
					'errors' => self::parse_only_text($this->errors)
				]
			);

			// Запись заголовков ответа
			header('Content-Length: ' . ob_get_length());

			// Отправка и деинициализация буфера вывода
			ob_end_flush();
			flush();
		}
	}

	/**
	 * Скрыть (показать)
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function hide(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
				// Авторизован аккаунт администратора или оператора

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				if ($task instanceof _document) {
					// Найдена заявка

					// Изменение статуса скрытия
					$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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано изменение в базу данных

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
						$this->view->page = null;

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'hided' => $this->view->rows[0]->task['hided'],
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Удалить
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function remove(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора и магазина

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для редактирования типа работы этой заявки');

				// Заявка подтверждена?
				if ($task->confirmed) throw new exception('Запрещено удалять подтверждённую заявку');

				if ($this->account->type === 'market') {
					// Магазин

					// Инициализация даты
					$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

					// Инициализация времени
					$start = datetime::createFromFormat('H:i', (string) $task->start);
					$end = datetime::createFromFormat('H:i', (string) $task->end);

					// Перенос времени в дату
					$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 (time() - $start > 0)
						throw new exception('Запрещено удалять начатую заявку');

					// Заявка уже прошла?
					if (time() - $end > 0)
						throw new exception('Запрещено удалять прошедшую заявку');

					// Заявка уже завершена?
					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) {
					// Найдена заявка

					// Изменение статуса
					$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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Помечено как удалённое

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'deleted' => true,
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Прочитать значение по идентификатору
	 *
	 * @param array $parameters Параметры запроса
	 *
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function value(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора или магазина

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для чтения параметров этой заявки');

				if ($task instanceof _document) {
					// Найдена заявка

					// Запись заголовков ответа
					header('Content-Type: application/json');
					header('Content-Encoding: none');
					header('X-Accel-Buffering: no');

					// Инициализация буфера вывода
					ob_start();

					// Генерация ответа
					echo json_encode(
						[
							'value' => $task->{$parameters['name']} ?? null,
							'errors' => self::parse_only_text($this->errors)
						]
					);

					// Запись заголовков ответа
					header('Content-Length: ' . ob_get_length());

					// Отправка и деинициализация буфера вывода
					ob_end_flush();
					flush();
				} else throw new exception('Не найдена заявка');
			} else throw new exception('Вы не авторизованы');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$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();
		}
	}

	/**
	 * Прочитать данные работ для <datalist>
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function works(array $parameters = []): void
	{
		try {
			if (!empty($parameters['task'])) {
				// Запрошены данные работ по заявке

				if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market' || $this->account->type === 'worker')) {
					// Авторизован аккаунт администратора, оператора или магазина

					// Инициализация данных
					$this->view->task = model::read('d._key == "' . $parameters['task'] . '"');

					if ($this->view->task instanceof _document) {
						// Найдена заявка

						// Заявка не принадлежит запросившему магазину?
						if ($this->account->type === 'market' and $this->view->task->market !== account::market($this->account->getId())?->id)
							throw new exception('Вы не авторизованы для просмотра типа работы этой заявки');

						// Заявка не принадлежит запросившему сотруднику?
						if ($this->account->type === 'worker' and $this->view->task->worker !== account::worker($this->account->getId())?->id)
							throw new exception('Вы не авторизованы для просмотра типа работы этой заявки');

						// Инициализация списка работ
						$this->view->works = static::WORKS;

						// Проверка на существование записанной в задаче работы в списке существующих работ и запись об этом в глобальную переменную шаблонизатора
						foreach ($this->view->works as $work) if ($this->view->task->work === $work) $this->view->exist = true;

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'works' => $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'works.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не найдена заявка');
				} else throw new exception('Вы не авторизованы');
			} else if (!empty($parameters['worker'])) {
				// Запрошены данные работ по сотруднику

				if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'worker')) {
					// Авторизован аккаунт администратора, оператора или магазина

					// Инициализация данных
					$this->view->worker = worker::read('d.id == "' . $parameters['worker'] . '"');

					if ($this->view->worker instanceof _document) {
						// Найден сотрудник

						// Сотрудник не принадлежит запросившему аккаунту?
						if ($this->account->type === 'worker' and $this->view->worker->id !== account::worker($this->account->getId())?->id)
							throw new exception('Вы не авторизованы для просмотра типа работы этого сотрудника');

						// Инициализация списка работ
						$this->view->works = static::WORKS;

						// Проверка на существование записанной в задаче работы в списке существующих работ и запись об этом в глобальную переменную шаблонизатора
						foreach ($this->view->works as $work) if ($this->view->worker->work === $work) $this->view->exist = true;

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'works' => $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'works.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не найден сотрудник');
				} else throw new exception('Вы не авторизованы');
			} else {
				// Запрошен список работ

				if ($this->account->status()) {
					// Авторизован аккаунт

					// Инициализация списка работ
					$this->view->works = ['Кассир', 'Выкладчик', 'Гастроном', 'Бригадир', 'Грузчик', 'Мобильный грузчик', 'Мобильный универсал'];

					// Запись заголовков ответа
					header('Content-Type: application/json');
					header('Content-Encoding: none');
					header('X-Accel-Buffering: no');

					// Инициализация буфера вывода
					ob_start();

					// Генерация ответа
					echo json_encode(
						[
							'works' => $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'works.html'),
							'errors' => self::parse_only_text($this->errors)
						]
					);

					// Запись заголовков ответа
					header('Content-Length: ' . ob_get_length());

					// Отправка и деинициализация буфера вывода
					ob_end_flush();
					flush();
				} 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();
		}
	}

	/**
	 * Записать тип работы
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function work(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора или магазина

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для редактирования типа работы этой заявки');

				// Заявка подтверждена?
				if ($task->confirmed) throw new exception('Запрещено редактировать тип работы у подтверждённой заявки');

				// Инициализация даты
				$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

				// Инициализация времени
				$start = datetime::createFromFormat('H:i', (string) $task->start);
				$end = datetime::createFromFormat('H:i', (string) $task->end);

				// Перенос времени в дату
				$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 time() - $start > 0)
					throw new exception('Запрещено редактировать тип работы начатой заявки');

				// Заявка уже завершена
				if ($this->account->type === 'market' and $task->completed === true || time() - $end > 0)
					throw new exception('Запрещено редактировать тип работы завершённой заявки');

				if ($task instanceof _document) {
					// Найдена заявка

					// Изменение статуса
					$task->work = match ($parameters['work']) {
						'Кассир', 'Выкладчик', 'Грузчик', 'Гастроном' => $parameters['work'],
						default => 'Кассир'
					};

					// Запись в реестре последних обновивших
					$task->updates = [$this->account->type => match ($this->account->type) {
						'worker', 'market' => account::{$this->account->type}($this->account->getId())?->id,
						default => $this->account->getKey()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано в ArangoDB

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение)
						$this->view->page = null;

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'writed' => true,
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Записать описание
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function description(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора или магазина

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для редактирования описания этой заявки');

				// Заявка подтверждена?
				if ($task->confirmed) throw new exception('Запрещено редактировать описание у подтверждённой заявки');

				// Инициализация даты
				$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

				// Инициализация времени
				$start = datetime::createFromFormat('H:i', (string) $task->start);
				$end = datetime::createFromFormat('H:i', (string) $task->end);

				// Перенос времени в дату
				$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 time() - $start > 0)
					throw new exception('Запрещено редактировать описание начатой заявки');

				// Заявка уже завершена
				if ($this->account->type === 'market' and $task->completed === true || time() - $end > 0)
					throw new exception('Запрещено редактировать описание завершённой заявки');

				if ($task instanceof _document) {
					// Найдена заявка

					// Изменение статуса
					$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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано в ArangoDB

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'writed' => true,
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Записать дату и время
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function date(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
				// Авторизован аккаунт администратора, оператора или магазина

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				// Заявка не принадлежит запросившему магазину?
				if ($this->account->type === 'market' and $task->market !== account::market($this->account->getId())?->id)
					throw new exception('Вы не авторизованы для редактирования даты и времени этой заявки');

				// Заявка подтверждена?
				if ($task->confirmed) throw new exception('Запрещено редактировать дату и время у подтверждённой заявки');

				// Инициализация даты
				$date = (new DateTime('@' . $task->date))->setTimezone(new DateTimeZone('Asia/Krasnoyarsk'));

				// Инициализация времени
				$start = datetime::createFromFormat('H:i', (string) $task->start);
				$end = datetime::createFromFormat('H:i', (string) $task->end);

				// Перенос времени в дату
				$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 time() - $start > 0)
					throw new exception('Запрещено редактировать дату и время начатой заявки');

				// Заявка уже завершена
				if ($this->account->type === 'market' and $task->completed === true || time() - $end > 0)
					throw new exception('Запрещено редактировать дату и время завершённой заявки');

				if ($task instanceof _document) {
					// Найдена заявка

					// Запись даты и времени
					if (!empty($parameters['date'])) $task->date = +$parameters['date'];
					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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано в ArangoDB

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'writed' => true,
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Записать комментарий
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function commentary(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
				// Авторизован аккаунт администратора или оператора

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				if ($task instanceof _document) {
					// Найдена заявка

					// Запись комментария
					$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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано в ArangoDB

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'writed' => true,
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Опубликовать
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function publish(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
				// Авторизован аккаунт администратора или оператора

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				if ($task instanceof _document) {
					// Найдена заявка

					// Запись статуса о публикации
					$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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано в ArangoDB

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'published' => true,
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Снять с публикации
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function unpublish(array $parameters = []): void
	{
		try {
			if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
				// Авторизован аккаунт администратора или оператора

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				if ($task instanceof _document) {
					// Найдена заявка

					// Запись статуса о публикации
					$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()
					}] + ($task->updates ?? []);

					if (_core::update($task)) {
						// Записано в ArangoDB

						// Инициализация строки в глобальную переменную шаблонизатора
						$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

						// Запись заголовков ответа
						header('Content-Type: application/json');
						header('Content-Encoding: none');
						header('X-Accel-Buffering: no');

						// Инициализация буфера вывода
						ob_start();

						// Генерация ответа
						echo json_encode(
							[
								'unpublished' => true,
								'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
								'errors' => self::parse_only_text($this->errors)
							]
						);

						// Запись заголовков ответа
						header('Content-Length: ' . ob_get_length());

						// Отправка и деинициализация буфера вывода
						ob_end_flush();
						flush();
					} else throw new exception('Не удалось обновить заявку');
				} else throw new exception('Не найдена заявка');
			} 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();
		}
	}

	/**
	 * Сгенерировать чат
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function chat(array $parameters = []): void
	{
		try {
			if ($this->account->status()) {
				// Авторизован аккаунт (любой)

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				if ($task instanceof _document) {
					// Найдена заявка

					// Инициализация функции сортировки сообщений по дате
					function sort(array $a, array $b)
					{
						if ($a['date'] === $b['date']) return 0;
						return $a['date'] > $b['date'] ? 1 : -1;
					}

					if ($parameters['chat'] === 'worker') {
						// Сотрудник

						if (
							$this->account->type === 'operator'
							|| $this->account->type === 'administrator'
							|| ($this->account->type === 'worker'	&& $task->worker === account::worker($this->account->getId())?->id)
						) {
							// Авторизован аккаунт (если сотрудник, то назначен на эту заявку)

							// Инициализация буфера чата
							$chat = $task->chats;

							if ($this->account->type === 'worker') {
								// Сотрудник

								foreach ($task->chats['worker'] ?? [] as $key => $message) {
									// Перебор сообщений

									// Запись статуса о прочтении сообщения
									$chat = [
										'worker' => [
											$key => [
												'readed' => [
													$this->account->getKey() => true
												] + ($chat['worker'][$key]['readed'] ?? [])
											] + $chat['worker'][$key]
										] + $chat['worker']
									] + $chat;
								}
							} else if ($this->account->type === 'operator' || $this->account->type === 'administrator') {
								// Оператор или администратор

								// Из-за того, что отправляется сразу 2 запроса, то не успевает записаться как прочитанный чат либо сотрудника, либо магазина
								foreach ($task->chats ?? [] as $name => $messages)
									foreach ($messages as $key => $message) {
										// Перебор сообщений

										// Запись статуса о прочтении сообщения
										$chat = [
											$name => [
												$key => [
													'readed' => [
														$this->account->getKey() => true
													] + ($chat[$name][$key]['readed'] ?? [])
												] + $chat[$name][$key]
											] + $chat[$name]
										] + $chat;
									}
							}

							// Запись чата из буфера
							$task->chats = $chat;

							// Запись данных чтения сообщений в ArangoDB
							if (!_core::update($task))  throw new exception('Не удалось прочитать сообщения');

							// Запись сообщений в буфер сортировки
							$buffer = $task->chats['worker'] ?? [];

							// Сортировка
							if (count($buffer) > 1) uasort($buffer, __NAMESPACE__ . '\sort');

							// Запись из буфера сортировки в буфер шаблонизатора
							$this->view->messages = $buffer;
						} else throw new exception('Вы не авторизованы для чтения этого чата');
					} else if ($parameters['chat'] === 'market') {
						// Магазин

						if (
							$this->account->type === 'operator'
							|| $this->account->type === 'administrator'
							|| ($this->account->type === 'market' && $task->market === account::market($this->account->getId())?->id)
						) {
							// Авторизован аккаунт (если магазин, то назначен на эту заявку)

							// Инициализация буфера чата
							$chat = $task->chats;

							if ($this->account->type === 'market') {
								// Магазин 

								foreach ($task->chats['market'] ?? [] as $key => $message) {
									// Перебор сообщений

									// Запись статуса о прочтении сообщения
									$chat = [
										'market' => [
											$key => [
												'readed' => [
													$this->account->getKey() => true
												] + $chat['market'][$key]['readed'] ?? []
											] + $chat['market'][$key]
										] + $chat['market']
									] + $chat;
								}
							} else if ($this->account->type === 'operator' || $this->account->type === 'administrator') {
								// Оператор или администратор

								// Из-за того, что отправляется сразу 2 запроса, то не успевает записаться как прочитанный чат либо сотрудника, либо магазина
								foreach ($task->chats ?? [] as $name => $messages)
									foreach ($messages as $key => $message) {
										// Перебор сообщений

										// Запись статуса о прочтении сообщения
										$chat = [
											$name => [
												$key => [
													'readed' => [
														$this->account->getKey() => true
													] + ($chat[$name][$key]['readed'] ?? [])
												] + $chat[$name][$key]
											] + $chat[$name]
										] + $chat;
									}
							}

							// Запись чата из буфера
							$task->chats = $chat;

							// Запись данных чтения сообщений в ArangoDB
							if (!_core::update($task))  throw new exception('Не удалось прочитать сообщения');

							// Запись сообщений в буфер сортировки
							$buffer = $task->chats['market'] ?? [];

							// Сортировка
							if (count($buffer) > 1) uasort($buffer, __NAMESPACE__ . '\sort');

							// Запись из буфера сортировки в буфер шаблонизатора
							$this->view->messages = $buffer;
						} else throw new exception('Вы не авторизованы для чтения этого чата');
					}

					// Инициализация строки в глобальную переменную шаблонизатора
					$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

					// Запись заголовков ответа
					header('Content-Type: application/json');
					header('Content-Encoding: none');
					header('X-Accel-Buffering: no');

					// Инициализация буфера вывода
					ob_start();

					// Генерация ответа
					echo json_encode(
						[
							'chat' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'chat.html'),
							'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
							'errors' => self::parse_only_text($this->errors)
						]
					);

					// Запись заголовков ответа
					header('Content-Length: ' . ob_get_length());

					// Отправка и деинициализация буфера вывода
					ob_end_flush();
					flush();
				} else throw new exception('Не найдена заявка');
			} else throw new exception('Вы не авторизованы');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$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();
		}
	}

	/**
	 * Записать сообщение
	 *
	 * @param array $parameters Параметры запроса
	 * 
	 * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
	 */
	public function message(array $parameters = []): void
	{
		try {
			if ($this->account->status()) {
				// Авторизован аккаунт (любой)

				// Инициализация данных
				$task = model::read('d._key == "' . $parameters['task'] . '"');

				if ($task instanceof _document) {
					// Найдена заявка

					if (mb_strlen($parameters['text']) > 0) {
						// Получено сообщение

						if ($parameters['chat'] === 'worker') {
							// Сотрудник

							if (
								$this->account->type === 'operator'
								|| $this->account->type === 'administrator'
								|| ($this->account->type === 'worker'	&& $task->worker === account::worker($this->account->getId())?->id)
							) {
								// Авторизован аккаунт (если сотрудник, то назначен на эту заявку)

								// Инициализация значения по умолчанию
								if (!isset($task->chats['worker'])) $task->chats = ['worker' => []] + ($task->chats ?? []);

								// Запись сообщения
								$task->chats = [
									'worker' => [
										count($task->chats['worker']) => [
											'from' => [
												'_key' => $this->account->getKey(),
												'type' => $this->account->type
											] + ($this->account->type === 'worker' ? ['id' => $task->worker] : []),
											'type' => $parameters['type'] ?? 'message',
											'text' => $parameters['text'],
											'readed' => [$this->account->getKey() => 0],
											'date' => time()
										]
									] + $task->chats['worker']
								] + $task->chats;

								if ($parameters['type'] === 'problem') {
									// Сообщение о проблеме

									// Изменение статуса наличия проблемы
									$task->problematic = true;
								} else if ($parameters['type'] === 'solution') {
									// Сообщение о решении проблемы

									// Изменение статуса наличия проблемы
									$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()
								}] + ($task->updates ?? []);

								if (_core::update($task)) {
									// Записано в ArangoDB

									// Инициализация строки в глобальную переменную шаблонизатора
									$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

									// Запись заголовков ответа
									header('Content-Type: application/json');
									header('Content-Encoding: none');
									header('X-Accel-Buffering: no');

									// Инициализация буфера вывода
									ob_start();

									// Генерация ответа
									echo json_encode(
										[
											'sended' => true,
											'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
											'errors' => self::parse_only_text($this->errors)
										]
									);

									// Запись заголовков ответа
									header('Content-Length: ' . ob_get_length());

									// Отправка и деинициализация буфера вывода
									ob_end_flush();
									flush();
								} else throw new exception('Не удалось отправить сообщение');
							} else throw new exception('Вы не авторизованы для чтения этого чата');
						} else if ($parameters['chat'] === 'market') {
							// Магазин

							if (
								$this->account->type === 'operator'
								|| $this->account->type === 'administrator'
								|| ($this->account->type === 'market' && $task->market === account::market($this->account->getId())?->id)
							) {
								// Авторизован аккаунт (если магазин, то назначен на эту заявку)

								// Инициализация значения по умолчанию
								if (!isset($task->chats['market'])) $task->chats = ['market' => []] + ($task->chats ?? []);

								// Запись сообщения
								$task->chats = [
									'market' => [
										count($task->chats['market']) => [
											'from' => [
												'_key' => $this->account->getKey(),
												'type' => $this->account->type
											] + ($this->account->type === 'market' ? ['id' => $task->market] : []),
											'type' => $parameters['type'] ?? 'message',
											'text' => $parameters['text'],
											'readed' => [$this->account->getKey() => 0],
											'date' => time()
										]
									] + $task->chats['market']
								] + $task->chats;

								if ($parameters['type'] === 'problem') {
									// Сообщение о проблеме

									// Изменение статуса наличия проблемы
									$task->problematic = true;
								} else if ($parameters['type'] === 'solution') {
									// Сообщение о решении проблемы

									// Изменение статуса наличия проблемы
									$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()
								}] + ($task->updates ?? []);

								if (_core::update($task)) {
									// Записано в ArangoDB

									// Инициализация строки в глобальную переменную шаблонизатора
									$this->view->rows = static::preprocessing($this->account, model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1));

									// Запись заголовков ответа
									header('Content-Type: application/json');
									header('Content-Encoding: none');
									header('X-Accel-Buffering: no');

									// Инициализация буфера вывода
									ob_start();
									// Генерация ответа
									echo json_encode(
										[
											'sended' => true,
											'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'),
											'errors' => self::parse_only_text($this->errors)
										]
									);

									// Запись заголовков ответа
									header('Content-Length: ' . ob_get_length());

									// Отправка и деинициализация буфера вывода
									ob_end_flush();
									flush();
								} else throw new exception('Не удалось отправить сообщение');
							} else throw new exception('Вы не авторизованы для чтения этого чата');
						}
					} else throw new exception('Необходимо передать сообщение');
				} else throw new exception('Не найдена заявка');
			} else throw new exception('Вы не авторизованы');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$this->errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];

			// Запись заголовков ответа
			header('Content-Type: application/json');
			header('Content-Encoding: none');
			header('X-Accel-Buffering: no');

			// Инициализация буфера вывода
			ob_start();

			// Генерация ответа
			echo json_encode(
				[
					'sended' => false,
					'errors' => self::parse_only_text($this->errors)
				]
			);

			// Запись заголовков ответа
			header('Content-Length: ' . ob_get_length());

			// Отправка и деинициализация буфера вывода
			ob_end_flush();
			flush();
		}
	}
}
