<?php

declare(strict_types=1);

namespace mirzaev\ebala\controllers;

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

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

// Встроенные библиотеки
use exception,
	datetime;

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

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

	/**
	 * Главная страница
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function index(array $parameters = []): ?string
	{
		// Авторизация
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт оператора или администратора

			foreach (['active', 'inactive', 'fined', 'decent', 'hided', 'fired'] as $name) {
				// Перебор фильтров статусов

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

				// Инициализировано значение?
				if ($value === null || $value === 0) continue;

				// Генерация класса для HTML-элемента по его состоянию (0 - ОТСУТСТВУЕТ, 1 - И, 2 - ИЛИ)
				$this->view->{$name} = match ($value) {
					'0', 0 => 'earth',
					'1', 1 => 'sand',
					'2', 2 => 'river',
					default => 'earth'
				};
			}

			// Генерация представлениямя
			$main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'workers.html');
		} else $main = $this->authorization();

		// Возврат (успех)
		if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]);
		else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main;

		// Возврат (провал)
		return null;
	}

	/**
	 * Прочитать
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function read(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт оператора или администратора

			// Реинициализация актуальной страницы
			if (isset($parameters['page'])) $this->session->write(['workers' => ['page' => $parameters['page']]]);
			else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['workers']['page'])) $this->session->write(['workers' => ['page' => 1]]);

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

			foreach (['active', 'inactive', 'fined', 'decent', 'hided', 'fired'] as $name) {
				// Перебор фильтров статусов

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

				// Конвертация ярлыков
				$converted = match ($name) {
					'inactive' => 'active',
					'decent' => 'fined',
					default => $name
				};

				$target = match ($converted) {
					'active' => 'account',
					default => 'worker'
				};

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

				// Генерация AQL-выражения для инъекции в строку запроса
				if ($value === '1') ${'filters_statuses_' . ($target === 'account' ? 'before' : 'after') . '_and'} .= " && " . $expression;
				else if ($value === '2') ${'filters_statuses_' . ($target === 'account' ? 'before' : 'after') . '_or'} .= " || " . $expression;
			}

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

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

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

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

			// Инициализация строки поиска
			$search = $_COOKIE["workers_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['workers']['filters']['search'] ?? '';
			if (mb_strlen($search) < 3) $search = null;
			$search_query = empty($search)
				? null
				: <<<AQL
					SEARCH 
						a.commentary IN TOKENS(@search, 'text_ru') 
						|| a.address IN TOKENS(@search, 'text_ru') 
						|| a.passport IN TOKENS(@search, 'text_ru') 
						|| a.department.address IN TOKENS(@search, 'text_ru') 
						|| a.requisites IN TOKENS(@search, 'text_ru') 
						|| STARTS_WITH(a._key, @search)
						|| STARTS_WITH(a.id, @search)
						|| STARTS_WITH(a.name.first, @search)
						|| STARTS_WITH(a.name.second, @search)
						|| STARTS_WITH(a.name.last, @search)
						|| STARTS_WITH(a.address, @search)
						|| STARTS_WITH(a.city, @search)
						|| STARTS_WITH(a.district, @search)
						|| STARTS_WITH(a.number, @search)
						|| STARTS_WITH(a.mail, @search)
						|| STARTS_WITH(a.passport, @search)
						|| STARTS_WITH(a.department.number, @search)
						|| STARTS_WITH(a.department.address, @search)
						|| STARTS_WITH(a.requisites, @search)
						|| STARTS_WITH(a.tax, @search)
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a._key, TOKENS(@search, 'text_en')[0], 2, true))
						|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.id, TOKENS(@search, 'text_en')[0], 1, true))
						|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.first, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.second, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 3 && LEVENSHTEIN_MATCH(a.name.last, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(a.address, TOKENS(@search, 'text_ru')[0], 2, true))
						|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.city, TOKENS(@search, 'text_ru')[0], 1, true))
						|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.district, TOKENS(@search, 'text_ru')[0], 1, true))
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.number, TOKENS(@search, 'text_en')[0], 2, true))
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.mail, TOKENS(@search, 'text_en')[0], 2, true))
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.passport, TOKENS(@search, 'text_ru')[0], 1, true))
						|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(a.department.number, TOKENS(@search, 'text_en')[0], 1, true))
						|| (LENGTH(@search) > 5 && LEVENSHTEIN_MATCH(a.department.address, TOKENS(@search, 'text_ru')[0], 1, true))
						|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(a.requisites, TOKENS(@search, 'text_ru')[0], 1, true))
						|| (LENGTH(@search) > 7 && LEVENSHTEIN_MATCH(a.tax, TOKENS(@search, 'text_ru')[0], 1, true))
					OPTIONS { collections: ["account", "worker"] }
					AQL;

			// Инициализация данных для генерации HTML-документа с таблицей
			$this->view->rows = registry::workers(
				before: sprintf(
					<<<AQL
						%s
						FILTER a.deleted != true
					AQL,
					$search_query,
				),
				after: sprintf(
					<<<AQL
						let account = (IS_SAME_COLLECTION('account', a) ? a : b)
						let worker = (IS_SAME_COLLECTION('worker', a) ? a : b)
						FILTER account.type == 'worker' && b.deleted != true
						%s
						%s
					AQL,
					empty($filters_before) ? null : "FILTER $filters_before",
					empty($filters_after) ? null : "FILTER $filters_after"
				),
				page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['workers']['page'],
				target: empty($search) ? account::COLLECTION : 'registry_accounts',
				binds: empty($search) ? [] : [
					'search' => $search
				]
			);

			// Запись в cookie (только таким методом можно записать "hostonly: true")		
			setcookie(
				'workers_page',
				(string) $this->session->buffer[$_SERVER['INTERFACE']]['workers']['page'],
				[
					'expires' => strtotime('+1 hour'),
					'path' => '/',
					'secure' => true,
					'httponly' => false,
					'samesite' => 'strict'
				]
			);

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

			// Инициализация блока
			return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'workers.html');
		}

		// Возврат (провал)
		return null;
	}

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

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

			try {
				// Проверка наличия переданного пароля
				if (!isset($parameters['account_password'])) throw new exception('Не получен пароль');
				else if (strlen($parameters['account_password']) < 6) throw new exception('Пароль должен быть не менее 6 символов');
				else if (!empty($parameters['worker_number']) && strlen($parameters['worker_number']) < 11) throw new exception('Несоответствие формату SIM-номера сотрудника');
				else if (!empty($parameters['account_number']) && strlen($parameters['account_number']) < 11) throw new exception('Несоответствие формату SIM-номера аккаунта сотрудника');
				else if (!empty($parameters['worker_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['worker_mail']) === 0) throw new exception('Несоответствие формату почты сотрудника');
				else if (!empty($parameters['account_mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['account_mail']) === 0) throw new exception('Несоответствие формату почты аккаунта сотрудника');
				else if (!empty($parameters['worker_id']) && model::read('d.id == "' . $parameters['worker_id'] . '"', errors: $this->errors['account']) instanceof _document) throw new exception('Уже существует сотрудник с данным идентификатором');

				// Универсализация
				/* $parameters['worker_number'] = (int) $parameters['worker_number']; */
				/* $parameters['account_number'] = (int) $parameters['account_number']; */
				if (!empty($parameters['requisites']) && $parameters['worker_requisites'][-1] === '.') $parameters['worker_requisites'] .= '.';
				if (!empty($parameters['worker_birth'])) $parameters['worker_birth'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_birth'])->getTimestamp();
				if (!empty($parameters['worker_issued'])) $parameters['worker_issued'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_issued'])->getTimestamp();
				if (!empty($parameters['work'])) $parameters['work'] = in_array($parameters['work'], static::WORKS) ? $parameters['work'] : static::WORKS[0];
				if (!empty($parameters['worker_hiring'])) $parameters['worker_hiring'] = DateTime::createFromFormat('Y-m-d', $parameters['worker_hiring'])->getTimestamp();

				// Создание аккаунта
				$account = account::create(
					data: [
						'type' => 'worker',
						'name' => [
							'first' => $parameters['account_name_first'],
							'second' => $parameters['account_name_second'],
							'last' => $parameters['account_name_last']
						],
						'number' => $parameters['account_number'],
						'mail' => $parameters['account_mail'],
						'password' => sodium_crypto_pwhash_str(
							$parameters['account_password'],
							SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
							SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
						),
						'commentary' => $parameters['account_commentary']
					],
					errors: $this->errors['account']
				);

				// Проверка существования созданного аккаунта
				if (empty($account)) throw new exception('Не удалось создать аккаунт');
			} catch (exception $e) {
				// Write to the errors registry
				$this->errors['account'][] = [
					'text' => $e->getMessage(),
					'file' => $e->getFile(),
					'line' => $e->getLine(),
					'stack' => $e->getTrace()
				];
			}

			// Инициализация идентификатора сотрудника
			$id = empty($parameters['worker_id']) ? model::id() : $parameters['worker_id'];

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

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

			// Генерация ответа
			echo json_encode(
				[
					'clipboard' => empty($this->errors['account']) ? <<<TEXT
					Номер: {$parameters['account_number']}
					Пароль: {$parameters['account_password']}
					TEXT : '',
					'errors' => self::parse_only_text($this->errors['account'])
				]
			);

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

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

			try {
				if (isset($account)) {
					// Инициализирован аккаунт

					// Создание сотрудника
					$worker = model::create(
						data: [
							'id' => (string) $id,
							'name' => [
								'first' => $parameters['worker_name_first'],
								'second' => $parameters['worker_name_second'],
								'last' => $parameters['worker_name_last']
							],
							'number' => $parameters['worker_number'],
							'mail' => $parameters['worker_mail'],
							'birth' => $parameters['worker_birth'],
							'passport' => $parameters['worker_passport'],
							'issued' => $parameters['worker_issued'],
							'department' => [
								'number' => $parameters['worker_department_number'],
								'address' => $parameters['worker_department_address']
							],
							'requisites' => $parameters['worker_requisites'],
							'payment' => $parameters['worker_payment'],
							'tax' => $parameters['worker_tax'],
							'city' => $parameters['worker_city'],
							'district' => $parameters['worker_district'],
							'address' => $parameters['worker_address'],
							'work' => $parameters['worker_work'],
							'hiring' => $parameters['worker_hiring'],
							'rating' => 3
						],
						errors: $this->errors['account']
					);

					// Проверка существования созданного сотрудника
					if (empty($worker)) throw new exception('Не удалось создать сотрудника');

					// Создание ребра: account -> worker
					account::connect($account, $worker, 'worker', $this->errors['account']);
				}
				throw new exception('Не инициализирован аккаунт');
			} catch (exception $e) {
				// Write to the errors registry
				$this->errors['account'][] = [
					'text' => $e->getMessage(),
					'file' => $e->getFile(),
					'line' => $e->getLine(),
					'stack' => $e->getTrace()
				];
			}
		}
	}

	/**
	 * Прочитать данные
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function fields(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт администратора или оператора

			// Инициализация данных сотрудника
			$worker = model::read('d.id == "' . $parameters['id'] . '"', return: '{ name: d.name, number: d.number, mail: d.mail, birth: d.birth, passport: d.passport, issued: d.issued, department: d.department, requisites: d.requisites, payment: d.payment, tax: d.tax, city: d.city, district: d.district, address: d.address, worl: d.work, hiring: d.hiring}')->getAll();

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

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

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

				// Генерация ответа
				echo json_encode($worker);

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

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

		// Возврат (провал)
		return null;
	}

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

			// Инициализация данных сотрудника
			$worker = model::read('d.id == "' . $parameters['id'] . '"');

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

				// Универсализация
				if (!empty($parameters['birth'])) $parameters['birth'] = DateTime::createFromFormat('Y-m-d', $parameters['birth'])->getTimestamp();
				if (!empty($parameters['issued'])) $parameters['issued'] = DateTime::createFromFormat('Y-m-d', $parameters['issued'])->getTimestamp();
				if (!empty($parameters['work'])) $parameters['work'] = in_array($parameters['work'], static::WORKS) ? $parameters['work'] : static::WORKS[0];
				if (!empty($parameters['hiring'])) $parameters['hiring'] = DateTime::createFromFormat('Y-m-d', $parameters['hiring'])->getTimestamp();

				// Инициализация параметров (перезапись переданными значениями)
				if ($parameters['name_first'] !== $worker->name['first']) $worker->name = ['first' => $parameters['name_first']] + $worker->name;
				if ($parameters['name_second'] !== $worker->name['second']) $worker->name = ['second' => $parameters['name_second']] + $worker->name;
				if ($parameters['name_last'] !== $worker->name['last']) $worker->name = ['last' => $parameters['name_last']] + $worker->name;
				if ($parameters['number'] !== $worker->number)
					if (mb_strlen($parameters['number']) === 11) $worker->number = $parameters['number'];
					else throw new exception('Номер должен состоять из 11 символов');
				if ($parameters['mail'] !== $worker->mail) $worker->mail = $parameters['mail'];
				if ($parameters['birth'] !== $worker->birth) $worker->birth = $parameters['birth'];
				if ($parameters['passport'] !== $worker->passport) $worker->passport = $parameters['passport'];
				if ($parameters['issued'] !== $worker->issued) $worker->issued = $parameters['issued'];
				if ($parameters['department_number'] !== $worker->department['number']) $worker->department = ['number' => $parameters['department_number']] + $worker->department;
				if ($parameters['department_address'] !== $worker->department['address']) $worker->department = ['address' => $parameters['department_address']] + $worker->department;
				if ($parameters['requisites'] !== $worker->requisites) $worker->requisites = $parameters['requisites'];
				if ($parameters['payment'] !== $worker->payment) $worker->payment = $parameters['payment'];
				if ($parameters['tax'] !== $worker->tax) $worker->tax = $parameters['tax'];
				if ($parameters['city'] !== $worker->city) $worker->city = $parameters['city'];
				if ($parameters['district'] !== $worker->district) $worker->district = $parameters['district'];
				if ($parameters['address'] !== $worker->address) $worker->address = $parameters['address'];
				if ($parameters['work'] !== $worker->work) $worker->work = $parameters['work'];
				if ($parameters['hiring'] !== $worker->hiring) $worker->hiring = $parameters['hiring'];

				if (_core::update($worker)) {
					// Записаны данные сотрудника

					// Инициализация строки в глобальную переменную шаблонизатора
					$this->view->rows = registry::workers(
						before: sprintf(
							"FILTER a._id == '%s' && a.deleted != true",
							$worker->getId()
						),
						after: <<<AQL
							let account = (IS_SAME_COLLECTION('account', a) ? a : b)
							let worker = (IS_SAME_COLLECTION('worker', a) ? a : b)
							FILTER account.type == 'worker' && b.deleted != true
						AQL,
						amount: 1,
						target: model::COLLECTION
					);

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

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

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

					// Инициализация буфера ответа
					$return = [
						'updated' => true,
						'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'workers.html'),
						'errors' => self::parse_only_text($this->errors)
					];

					// Генерация ответа
					echo json_encode($return);

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

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

		// Возврат (провал)
		return null;
	}

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

			// Инициализация данных сотрудника
			$worker = model::read('d.id == "' . $parameters['id'] . '"');

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

				// Увольнение
				$worker->active = false;
				$worker->fired = true;

				if (_core::update($worker)) {
					// Записаны данные сотрудника

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

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

					// Инициализация буфера ответа
					$return = [
						'fired' => true,
						'errors' => self::parse_only_text($this->errors)
					];

					// Генерация ответа
					echo json_encode($return);

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

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

		// Возврат (провал)
		return null;
	}

	/**
	 * Снять пометку уволенного (нанять)
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function hire(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
			// Авторизован аккаунт администратора или оператора

			// Инициализация данных сотрудника
			$worker = model::read('d.id == "' . $parameters['id'] . '"');

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

				// Увольнение
				$worker->active = true;
				$worker->fired = false;

				if (_core::update($worker)) {
					// Записаны данные сотрудника

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

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

					// Инициализация буфера ответа
					$return = [
						'hired' => true,
						'errors' => self::parse_only_text($this->errors)
					];

					// Генерация ответа
					echo json_encode($return);

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

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

		// Возврат (провал)
		return null;
	}

	/**
	 * Прочитать данные сотрудников для <datalist>
	 *
	 * @param array $parameters Параметры запроса
	 */
	public function datalist(array $parameters = []): ?string
	{
		if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) {
			// Авторизован аккаунт оператора или магазина

			// Инициализация данных сотрудников
			$this->view->workers = model::read(filter: 'd.active == true', amount: 10000, return: '{ id: d.id, name: d.name }');

			// Универсализация
			if ($this->view->workers instanceof _document) $this->view->workers = [$this->view->workers];

			// Возврат (успех)
			return $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'workers.html');
		}

		// Возврат (провал)
		return null;
	}
}
