Compare commits

...

2 Commits

76 changed files with 1867 additions and 2094 deletions

View File

@ -1,42 +1,43 @@
{ {
"name": "mirzaev/tordv-calculator", "name": "mirzaev/zkmr-calculator",
"description": "Калькулятор стоимости услуг по обработке металла", "description": "Калькулятор стоимости услуг по обработке металла",
"type": "site", "type": "site",
"keywords": [ "keywords": [
"site", "site",
"calculator" "calculator"
], ],
"readme": "README.md", "readme": "README.md",
"homepage": "https://git.mirzaev.sexy/mirzaev/tordv-calculator", "homepage": "https://git.mirzaev.sexy/mirzaev/zkmr-calculator",
"license": "WTFPL", "license": "WTFPL",
"authors": [ "authors": [
{ {
"name": "Arsen Mirzaev Tatyano-Muradovich", "name": "Arsen Mirzaev Tatyano-Muradovich",
"email": "arsen@mirzaev.sexy", "email": "arsen@mirzaev.sexy",
"homepage": "https://mirzaev.sexy", "homepage": "https://mirzaev.sexy",
"role": "Programmer" "role": "Programmer"
}
],
"support": {
"email": "arsen@mirzaev.sexy",
"docs": "https://git.mirzaev.sexy/mirzaev/tordv-calculator/wiki",
"issues": "https://git.mirzaev.sexy/mirzaev/tordv-calculator/issues"
},
"funding": [
{
"type": "funding",
"url": "https://fund.mirzaev.sexy"
}
],
"require": {
"php": "^8.0.0",
"mirzaev/minimal": "^2.0.x-dev",
"twig/twig": "^3.3",
"phpoffice/phpspreadsheet": "^1.20"
},
"autoload": {
"psr-4": {
"mirzaev\\tordv\\calculator\\": "mirzaev/tordv/calculator/system"
}
} }
],
"support": {
"email": "arsen@mirzaev.sexy",
"docs": "https://git.mirzaev.sexy/mirzaev/zkmr-calculator/wiki",
"issues": "https://git.mirzaev.sexy/mirzaev/zkmr-calculator/issues"
},
"funding": [
{
"type": "funding",
"url": "https://fund.mirzaev.sexy"
}
],
"require": {
"php": "^8.2.0",
"mirzaev/minimal": "^2.0.x-dev",
"twig/twig": "^3.3",
"phpoffice/phpspreadsheet": "^1.20",
"ext-mysqli": "*"
},
"autoload": {
"psr-4": {
"mirzaev\\zkmr\\calculator\\": "mirzaev/zkmr/calculator/system"
}
}
} }

5
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d63372480c8f1c3719fb9e69026eef21", "content-hash": "c424f9f2f1b7a9cbcfaa900a5da9d892",
"packages": [ "packages": [
{ {
"name": "ezyang/htmlpurifier", "name": "ezyang/htmlpurifier",
@ -926,7 +926,8 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^8.0.0" "php": "^8.2.0",
"ext-mysqli": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.3.0" "plugin-api-version": "2.3.0"

View File

@ -1,266 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers;
use Exception;
use mirzaev\tordv\calculator\controllers\core;
use mirzaev\tordv\calculator\models\calculators_model as calculators;
use mirzaev\tordv\calculator\models\settings_model as settings;
use mirzaev\tordv\calculator\models\metals_model as metals;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
/**
* Контроллер основной страницы
*
* @package mirzaev\tordv\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class calculator_controller extends core
{
/**
* Калькулятор
*
* HTML-код с калькулятором
*
* @param array $vars Параметры
*/
public function index(array $vars = []): ?string
{
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculator' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
/**
* Модуль: "тип пользователя"
*
* HTML-код с кнопками: "физическое лицо" и "юридическое лицо"
*
* @param array $vars Параметры
*/
public function buyer(array $vars = []): ?string
{
// Инициализация параметров
$vars['buyer'] = $vars['value'] ?? 'individual';
// Удаление параметров
unset($vars['value']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'buyer.html', $vars);
}
/**
* Модуль: "сложность"
*
* HTML-код с кнопками: "легко", "средне" и "сложно"
*
* @param array $vars Параметры
*/
public function complexity(array $vars = []): ?string
{
// Инициализация параметров
$vars['complexity'] = $vars['value'] ?? 'medium';
// Удаление параметров
unset($vars['value']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'complexity.html', $vars);
}
/**
* Модуль: "меню"
*
* HTML-код с кнопками добавления калькуляторов
*
* @param array $vars Параметры
*/
public function menu(array $vars = []): ?string
{
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'menu.html', $vars);
}
/**
* Модуль: "результат"
*
* HTML-код с данными результата калькуляции
*
* @param array $vars Параметры
*/
public function result(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация данных калькулятора
$vars['discount'] = settings::read('discount', $vars['errors']['calculators']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'result.html', $vars);
}
/**
* Модуль: "марка"
*
* HTML-код со списком марок металла
*
* @param array $vars Параметры
*
* @todo 1. Если металл свой, то ничего не генерировать
*/
public function mark(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация списка марок
$vars['marks'] = metals::marks(empty($vars['type']) ? settings::read("default_type") : $vars['type'], $vars['errors'], $vars['errors']['calculators']);
// Инициализация значения по умолчанию
if (empty($vars['marks'])) $vars['marks'] = ['Не найдено'];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'metals' . DIRECTORY_SEPARATOR . 'mark.html', $vars);
}
/**
* Модуль: "разделитель"
*
* HTML-код с разделителем элементов
*
* @param array $vars Параметры
*/
public function divider(array $vars = []): ?string
{
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'divider.html', $vars);
}
/**
* Лазерная резка
*
* HTML-код с калькулятором лазерной резки
*
* @param array $vars Параметры
*
* @todo 1. Заголовок калькулятора должен находиться внутри элемента калькулятора
* 2. Ограничение значений полей в зависимости от выбранной марки
*/
public function laser(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация данных калькулятора
$vars['calculators'] = ['laser' => [
'company' => settings::read('default_buyer', $vars['errors']['calculators']),
'complexity' => settings::read('default_complexity', $vars['errors']['calculators']),
'width' => (int) round((float) settings::read('default_width', $vars['errors']['calculators'])),
'height' => (int) round((float) settings::read('default_height', $vars['errors']['calculators'])),
'length' => (int) round((float) settings::read('default_length', $vars['errors']['calculators'])),
'amount' => (int) round((float) settings::read('default_amount', $vars['errors']['calculators'])),
'type' => settings::read('default_type', $vars['errors']['calculators']),
'holes' => (int) round((float) settings::read('default_holes', $vars['errors']['calculators'])),
'diameter' => (float) settings::read('default_diameter', $vars['errors']['calculators'])
]];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'laser.html', $vars);
}
/**
* Расчёт
*
* Генерирует ответ в виде ['expenses' => 0, 'income' => 0, 'profit' => 0]
*
* @param array $vars Параметры
*
* @todo
* 5. Убрать передачу цены работы (оставить только время работы в часах и цену за работу в час)
*/
public function calculate(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
try {
// Инициализация параметров из тела запроса (подразумевается, что там массивы с параметрами)
$vars['input'] = json_decode(file_get_contents('php://input'), true);
$calculators = $vars['input']['calculators'];
$discount = $vars['input']['discount'];
$cutting = $vars['input']['cutting'];
// Инициализация переменных для буфера вывода
$machines = $managers = $engineers = $operators = $handymans = $other = 0;
if (count($calculators) > 0) {
// Найдены калькуляторы
foreach ($calculators as $i => $calculator) {
// Перебор калькуляторов
foreach (['calculator'] as &$parameter) {
// Перебор мета-параметров
// Инициализация общего параметра
$type = $calculator[$parameter];
// Инициализация параметра для обработчика калькулятора
unset($calculator[$parameter]);
}
// Инициализация номера калькулятора в его категории
$number = count($vars['errors']['calculators'][$type] ?? []);
// Инициализация журнала ошибок для калькулятора
$calculator['errors'] = [];
// Инициализация журнала ошибок для буфера вывода
$vars['errors']['calculators'][$type][$number] = &$calculator['errors'];
// Инициализация буфера параметров
$parameters = [];
// Инициализация параметра типа покупателя (подразумевается, что если не "entity", то "individual")
$parameters['company'] = $calculator['buyer'] === 'entity';
unset($calculator['buyer']);
// Перенос остальных параметров в буфер параметров
$parameters += $calculator;
// Расчёт
[$machines, $managers, $engineers, $operators, $handymans, $other] = calculators::$type(...$parameters + ['cutting' => $cutting]);
}
} else {
// Не найдены калькуляторы
throw new exception('Не найдены калькуляторы');
}
} catch (exception $e) {
// Запись в журнал ошибок
$vars['errors']['calculators'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return json_encode([
'machines' => $machines,
'managers' => $managers,
'engineers' => $engineers,
'operators' => $operators,
'handymans' => $handymans,
'other' => $other + ['discount' => $discount],
'errors' => $vars['errors']
]);
}
}

View File

@ -1,621 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\tordv\calculator\models;
use pdo;
use exception;
/**
* Модель регистрации, аутентификации и авторизации
*
* @package mirzaev\tordv\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class accounts_model extends core
{
/**
* Регистрация
*
* @param string $name Входной псевдоним
* @param string $email Почта
* @param string $password Пароль (password)
* @param bool $authentication Автоматическая аутентификация в случае успешной регистрации
* @param array &$errors Журнал ошибок
*
* @return array|bool Аккаунт, если удалось аутентифицироваться
*/
public static function registration(string $name = null, string $email = null, string $password, array &$errors = []): array
{
try {
if (static::account($errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $name]) or $account = static::read(['email' => $email]))) {
// Не удалось найти аккаунт
if (static::write($name, $email, $password, $errors)) {
// Удалось зарегистрироваться
return $account;
}
} else {
// Удалось найти аккаунт
return $account;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Аутентификация
*
* @param string $login Входной псевдоним
* @param string $password Пароль (password)
* @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*/
public static function authentication(string $login, string $password, bool $remember = false, array &$errors = []): array
{
try {
if (static::account($errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $login]) or $account = static::read(['email' => $login]))) {
// Не удалось найти аккаунт
throw new exception('Не удалось найти аккаунт');
}
if (password_verify($password, $account['password'])) {
// Совпадают хеши паролей
// Инициализация идентификатора сессии
session_id($account['id']);
// Инициализация названия сессии
session_name('id');
// Инициализация сессии
session_start();
// Инициализация времени хранения хеша
$time = time() + ($remember ? 604800 : 86400);
// Инициализация хеша
$hash = static::hash((int) $account['id'], crypt($account['password'], time() . $account['id']), $time, $errors)['hash'];
// Инициализация cookies
setcookie("hash", $hash, $time, path: '/', secure: true);
return $account;
} else {
// Не совпадают хеши паролей
throw new exception('Неправильный пароль');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Аутентификация
*
* @param array &$errors Журнал ошибок
*
* @return bool Удалось ли деаутентифицироваться
*/
public static function deauthentication(array &$errors = []): bool
{
try {
if ($account = static::account($errors)) {
// Аутентифицирован пользователь
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = null, `time` = 0 WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $account['id'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
// Деинициализация cookies
setcookie("id", '', 0, path: '/', secure: true);
setcookie("hash", '', 0, path: '/', secure: true);
return true;
} else {
// Не аутентифицирован пользователь
// Запись ошибки
throw new exception('Не аутентифицирован');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Прочитать данные аккаунта, если пользователь аутентифицирован
*
* Можно использовать как проверку на аутентифицированность
*
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*
* @todo 1. Сделать в static::read() возможность передачи нескольких параметров и перенести туда непосредственно чтение аккаунта с проверкой хеша
*/
public static function account(array &$errors = []): array
{
try {
if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
// Аутентифицирован аккаунт (найдены cookie и они хранят значения - подразумевается, что не null или пустое)
if ($_COOKIE['hash'] === static::hash((int) $_COOKIE['id'], errors: $errors)['hash']) {
// Совпадает переданный хеш с тем, что хранится в базе данных
} else {
// Не совпадает переданный хеш с тем, что хранится в базе данных
// Генерация ошибки
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `id` = :id && `hash` = :hash");
// Параметры запроса
$params = [
":id" => $_COOKIE['id'],
":hash" => $_COOKIE['hash'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($account = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдена связка идентификатора с хешем
// Генерация ошибки
throw new exception('Не найден пользотватель или время аутентификации истекло');
}
// Чтение разрешений
$account['permissions'] = static::permissions((int) $account['id'], $errors);
return $account;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Прочитать разрешения аккаунта
*
* @param int $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
*
* @return array Разрешения аккаунта, если найдены
*/
public static function permissions(int $id, array &$errors = []): array
{
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($response = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдены разрешения
// Генерация ошибки
throw new exception('Не найдены разрешения');
}
// Удаление ненужных данных
unset($response['id']);
return $response;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Проверить разрешение
*
* @param string $permission Разрешение
* @param int|null $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
*
* @return bool|null Статус разрешения, если оно записано
*/
public static function access(string $permission, int|null $id = null, array &$errors = []): ?bool
{
try {
// Инициализация аккаунта
$account = isset($id) ? self::read(['id' => $id], $errors) : self::account($errors);
return isset($account['permissions'][$permission]) ? (bool) $account['permissions'][$permission] : null;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Запись пользователя в базу данных
*
* @param string|null $name Имя
* @param string|null $email Почта
* @param string|null $password Пароль
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*/
public static function write(string|null $name = null, string|null $email = null, string|null $password = null, array &$errors = []): array
{
try {
// Инициализация параметров запроса
$params = [];
if (isset($name)) {
try {
// Проверка параметра
if (iconv_strlen($name) < 3) throw new exception('Длина имени должна быть не менее 3 символов');
if (iconv_strlen($name) > 60) throw new exception('Длина имени должна быть не более 60 символов');
// Запись в буфер параметров запроса
$params[':name'] = $name;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($email)) {
try {
// Проверка параметра
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту');
if (iconv_strlen($email) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($email) > 60) throw new exception('Длина почты должна быть не более 80 символов');
// Запись в буфер параметров запроса
$params[':email'] = $email;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($password)) {
try {
// Проверка параметра
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
// Запись в буфер параметров запроса
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? '`email`' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? ':email' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
try {
if (isset($name)) {
// Передано имя аккаунта
// Чтение аккаунта
$account = static::read(['name' => $name]);
} else if (isset($email)) {
// Передана почта аккаунта
// Чтение аккаунта
$account = static::read(['email' => $email]);
} else {
// Не передано ни имя, ни почта
throw new exception('Не переданны данные для полноценной регистрации аккаунта');
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `permissions` (`id`) VALUES (:id)");
// Инициализация параметров
$params = [
':id' => $account['id']
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Конец выполнения
end:
return isset($account) && $account ? $account : [];
}
/**
* Чтение пользователя из базы данных
*
* @param array $search Поиск ('поле' => 'значение'), работает только с одним полем
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт, если найден
*/
public static function read(array $search, array &$errors = []): array
{
try {
// Инициализация данных для поиска
$field = array_keys($search)[0] ?? null;
$value = $search[$field] ?? null;
if (empty($field)) {
// Получено пустое значение поля
// Запись ошибки
throw new exception('Пустое значение поля для поиска');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `$field` = :field LIMIT 1");
// Параметры запроса
$params = [
":field" => $value,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if ($account = $request->fetch(pdo::FETCH_ASSOC)) {
// Найден аккаунт
try {
if ($permissions = static::permissions((int) $account['id'], $errors)) {
// Найдены разрешения
// Запись в буфер данных аккаунта
$account['permissions'] = $permissions;
} else {
// Не найдены разрешения
throw new exception('Не удалось найти и прочитать разрешения');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
}
} else {
// Не найден аккаунт
throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return isset($account) && $account ? $account : [];
}
/**
* Запись или чтение хеша из базы данных
*
* @param int $id Идентификатор аккаунта
* @param int|null $hash Хеш аутентифиакции
* @param string|null $time Время хранения хеша
* @param array &$errors Журнал ошибок
*
* @return array ['hash' => $hash, 'time' => $time]
*/
public static function hash(int $id, string|null $hash = null, int|null $time = null, array &$errors = []): array
{
try {
if (isset($hash, $time)) {
// Переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => $hash,
":time" => $time,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} else {
// Не переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("SELECT `hash`, `time` FROM `accounts` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
extract((array) $request->fetch(pdo::FETCH_ASSOC));
if (!empty($response['time']) && $response['time'] <= time()) {
// Истекло время жизни хеша
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => null,
":time" => null,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$response = $request->fetch(pdo::FETCH_ASSOC);
// Генерация ошибки
throw new exception('Время аутентификации истекло');
}
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return ['hash' => $hash, 'time' => $time];
}
}

View File

@ -1,163 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\tordv\calculator\models;
use exception;
use pdo;
/**
* Модель баллонов
*
* @package mirzaev\tordv\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*
* @todo
* 1. Если длина реза баллона зависит от типа металла (учитывается) то перенести это в класс металлов
*/
final class baloons_model extends core
{
/**
* Используемый газ
*/
public string $gas;
/**
* Количество баллонов
*/
public float $amount = 0;
/**
* Цена всех баллонов
*/
public float $cost;
/**
* Вычисление используемого газа
*
* @param float $length Толщина металла
* @param array &$errors Журнал ошибок
*
* @return string|null Название газа
*/
public function gas(float $length, array &$errors = []): ?string
{
try {
return $this->gas = match (true) {
$length >= 4 => 'oxygen',
default => 'air'
};
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Вычисление количества используемых баллонов
*
* @param string $metal Тип металла
* @param float $cutting Длина реза
* @param float $length Толщина листа
* @param string|null $gas Используемый газ
* @param array &$errors Журнал ошибок
*
* @return int|null Количество баллонов
*
* @todo
* 1. Добавить к баллонам уточнение чтобы считало не по листам а по объёму а лучше по длине реза
* 2. Определение длины реза по типу металла
*/
public function amount(string $metal, float $cutting, float $length, ?string $gas = null, array &$errors = []): ?float
{
try {
// Инициализация входных параметров
$gas ?? $gas = &$this->gas;
// Инициализация запроса
$request = static::$db->prepare("SELECT `length` FROM `baloons` WHERE `gas` = :gas LIMIT 30");
// Отправка запроса
$request->execute([
':gas' => $gas
]);
// Генерация ответа
$response = $request->fetch(pdo::FETCH_ASSOC);
// Проверка на полученные значения
if (!is_array($response)) return null;
// Вычисление длины реза на которое хватит баллона
$flow = $response['length'] / $length;
// Вычисление количества баллонов (округление к большему)
return $this->amount = $cutting / $flow;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Вычисление стоимости баллонов
*
* @param string $metal Тип металла
* @param int|null $gas Используемый газ
* @param array &$errors Журнал ошибок
*
* @return float|null Стоимость баллонов
*/
public function cost(string $metal, ?int $amount = null, ?string $gas = null, array &$errors = []): ?float
{
try {
// Инициализация входных параметров
$amount ?? $amount = &$this->amount;
$gas ?? $gas = &$this->gas;
// Инициализация запроса
$request = static::$db->prepare("SELECT `cost` FROM `baloons` WHERE `gas` = :gas LIMIT 30");
// Отправка запроса
$request->execute([
':gas' => $gas
]);
// Генерация ответа
$response = $request->fetch(pdo::FETCH_ASSOC);
// Проверка на полученные значения
if (!is_array($response)) return null;
// Инициализация стоимости всех баллонов
return $this->cost = $response['cost'] * $amount;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
}

View File

@ -1,232 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\tordv\calculator\models;
use mirzaev\tordv\calculator\models\settings_model as settings;
use mirzaev\tordv\calculator\models\accounts_model as accounts;
use pdo;
use exception;
/**
* Модель металла
*
* @package mirzaev\tordv\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class metals_model extends core
{
/**
* Расчёт скорость реза металла
*
* @param string $metal Металл
* @param string $gas Газ
* @param float $lenght Толщина
* @param array &$errors Журнал ошибок
*
* @return float|int Скорость реза (мм/с)
*/
public static function cut(string $metal, string $gas, float $lenght, array &$errors = []): float|int
{
try {
return (float) settings::read("cut_speed_${metal}_${gas}_$lenght", $errors) ?? throw new exception('Не удалось определить скорость реза металла');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return 0;
}
/**
* Определение веса
*
* @param string $type Тип
* @param array &$errors Журнал ошибок
*
* @return float|null Вес (кг)
*/
public static function kg(string $type = 'stainless_steel', array &$errors = []): ?float
{
try {
return (float) match ($type) {
'steel' => 8,
'galvanized_steel' => 8,
'stainless_steel' => 8.7,
'brass' => 8.7,
'copper' => 9,
'aluminum' => 3,
default => throw new exception('Не удалось определить тип металла')
};
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Запись в базу данных
*
* @param int $supply Идентификатор записи поставки в базе данных
* @param string $type Тип
* @param string $mark Марка
* @param float $width Ширина (мм)
* @param float $height Высота (мм)
* @param float $length Толщина (мм)
* @param int $piece Цена за лист (руб)
* @param int $ton Цена за тонну (руб)
* @param array &$errors Журнал ошибок
*
* @return bool Статус записи
*/
public static function write(int $supply, string $type, string $mark, float $width, float $height, float $length, int $piece, int $ton, array &$errors = []): bool
{
try {
if ($account = accounts::account($errors)) {
// Инициализирован аккаунт
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `metals` (`supply`, `type`, `mark`, `width`, `height`, `length`, `piece`, `ton`, `account`) VALUES (:supply, :type, :mark, :width, :height, :length, :piece, :ton, :account)");
// Инициализация параметров
$params = [
':supply' => $supply,
':type' => $type,
':mark' => $mark,
':width' => $width,
':height' => $height,
':length' => $length,
':piece' => $piece,
':ton' => $ton,
':account' => $account['id'],
];
// Отправка запроса
$request->execute($params);
// Получение ответа
return $request->fetch(pdo::FETCH_ASSOC) !== false;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Чтение из базы данных
*
* Очищает от дубликатов
*
* @param string $type Тип
* @param string $mark Марка
* @param float $length Толщина
* @param array &$errors Журнал ошибок
*
* @return float|null Цена за 1 килограмм (руб)
*/
public static function read(string $type, string $mark, float $length, array &$errors = []): ?array
{
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT `width`, `height`, `piece`, `ton` FROM `metals` WHERE `type` = :type && `mark` = :mark && `length` = :length LIMIT 30");
// Отправка запроса
$request->execute([
':type' => $type,
':mark' => $mark,
':length' => $length
]);
// Генерация ответа
$response = $request->fetchAll(pdo::FETCH_ASSOC);
// Проверка на полученные значения
if (!is_array($response)) return false;
if (count($response) === 1) return $response[0];
else if (count($response) > 1) {
// Найдено более чем одно значение
// Инициализация буфера вывода
$buffer = $response[0];
foreach ($response as $metal) {
// Перебор полученных значений металлов
// Запись в буфер металла с самой большей площадью листа
if (($metal['width'] * $metal['height']) > ($buffer['width'] * $buffer['height'])) $buffer = $metal;
}
return $metal;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Поиск и чтение марок металла
*
* Очищает от дубликатов
*
* @param string $type Тип
* @param array &$errors Журнал ошибок
*
* @return float|null Цена за 1 килограмм (руб)
*/
public static function marks(string $type = '*', array &$errors = []): ?array
{
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT DISTINCT `mark` FROM `metals` WHERE `type` = :type");
// Отправка запроса
$request->execute([':type' => $type]);
// Генерация ответа
$response = $request->fetchAll(pdo::FETCH_COLUMN);
return $response === false ? null : (array) $response;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
}

View File

@ -1,577 +0,0 @@
'use strict';
let calculator = {
index: document.getElementById("calculator"),
calculators: [],
account: [],
settings: {
defaults: {
buyer: 'individual',
complexity: 'medium',
}
},
init() {
// Инициализация калькулятора
this.generate.buyer(this.settings.defaults.buyer)
.then(
success => {
this.generate.complexity(this.settings.defaults.complexity)
.then(
success => {
this.generate.menu()
.then(
success => {
this.authenticate(cookie.read('id'))
.then(
success => {
// Запись данных аккаунта
this.account = success;
if (this.account !== undefined && typeof this.account === 'object' && this.account.permissions !== undefined) {
// Найден аккаунт
if (this.account.permissions.calculate == 1) {
// Разрешено использовать калькулятор
this.generate.result();
}
}
}
);
}
);
}
);
}
);
console.log('[КАЛЬКУЛЯТОР] Инициализирован');
},
authenticate(id) {
// Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо)'
return fetch('/account/data?id=' + id, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
return response.json().then(
success => {
console.log('[КАЛЬКУЛЯТОР] Загружены данные пользователя: ' + id);
return success;
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить данные пользователя: ' + id);
});
}
});
},
calculate() {
// Запрос и генерация HTML с данными о рассчете со всех калькуляторов
// Инициализация параметров
let cutting = document.getElementById('cutting');
let discount = document.getElementById('discount');
// Инициализация буфера запроса
let query = {
calculators: {},
cutting: +cutting.value ?? 0,
discount: +discount.value ?? 0
};
for (const number in this.calculators) {
// Перебор калькуляторов
// Инициализация буфера запроса для нового калькулятора
query['calculators'][number] = {};
// Инициализация типа калькулятора
query['calculators'][number]['calculator'] = this.calculators[number].getAttribute('data-calculator');
for (const buyer of this.index.querySelectorAll('input[name="buyer"]')) {
// Перебор полей с параметрами типа заказчика
if (buyer.checked) {
// Найдено выбранное поле
// Запись в буфер запроса
query['calculators'][number]['buyer'] = buyer.value;
}
}
for (const complexity of this.index.querySelectorAll('input[name="complexity"]')) {
// Перебор полей с параметрами сложности
if (complexity.checked) {
// Найдено выбранное поле
// Запись в буфер запроса
query['calculators'][number]['complexity'] = complexity.value;
}
}
for (const field of this.calculators[number].querySelectorAll('[data-calculator-parameter]')) {
// Перебор полей с параметрами
if (field.getAttribute('type') === 'checkbox') {
// Флажок
// Запись в буфер запроса
query['calculators'][number][field.getAttribute('data-calculator-parameter')] = field.checked;
} else if (field.getAttribute('type') === 'text' || field.getAttribute('type') === 'number' || field.getAttribute('type') === 'range') {
// Текстовое, цифровое поле или ползунок
// Запись в буфер запроса
query['calculators'][number][field.getAttribute('data-calculator-parameter')] = field.value;
} else {
// Элемент с тегом <select> (подразумевается)
// Запись в буфер запроса
query['calculators'][number][field.getAttribute('data-calculator-parameter')] = field.value ?? field.options[field.selectedIndex].text;
}
}
}
return fetch('/calculator/calculate', {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(query)
}).then((response) => {
if (response.status === 200) {
response.json().then(
success => {
// Инициализация буфера расходов
let expenses = 0;
// Инициализация буфера с данными расчёта
let result;
if (this.generate.error(success.errors) > 0) {
// Найдены ошибки
// Генерация текста ответа
result = 'Ошибка';
} else {
// Не найдены ошибки
if (success.other.cutting !== undefined) {
// Получены данные времени работы
// Запись полученных данных
cutting.value = success.other.cutting;
cutting.parentElement.children[0].innerText = 'Длина реза 1 детали (' + cutting.value + 'мм)';
// Разблокировка параметра
cutting.removeAttribute('disabled');
}
if (success.other.discount !== undefined) {
// Получены данные скидки
// Запись полученных данных
discount.value = success.other.discount;
discount.parentElement.children[0].innerText = 'Скидка (' + discount.value + '%)';
}
for (const [, machine] of Object.entries(success.machines)) {
// Перебор станков
// Прибавление данных станка к буферу расходов
expenses += (machine.electricity + (machine.metal ?? 0));
// Прибавление амортизации к буферу вывода
expenses += machine.reprocessing ?? 0;
// Прибавление линз к буферу вывода
expenses += machine.lenses ?? 0;
}
for (const [, manager] of Object.entries(success.managers)) {
// Перебор менеджеров
// Прибавление данных менеджера к буферу расходов
expenses += manager.time * manager.hour;
}
for (const [, engineer] of Object.entries(success.engineers)) {
// Перебор инженеров
// Прибавление данных инженера к буферу расходов
expenses += engineer.time * engineer.hour;
}
for (const [, operator] of Object.entries(success.operators)) {
// Перебор операторов
// Прибавление данных оператора к буферу расходов
expenses += (operator.time.design + operator.time.machine) * operator.hour;
}
for (const [, handyman] of Object.entries(success.handymans)) {
// Перебор разнорабочих
// Прибавление данных к буферу расходов
expenses += handyman.time * handyman.hour;
}
// Прибавление аренды к буферу расходов
expenses += success.other.rent ?? 0;
// Прибавление переработки к буферу расходов
expenses += success.other.reprocessing ?? 0;
// Прибавление баллонов к буферу расходов
expenses += success.other.baloons.cost ?? 0;
// Вычисление наценки (коэффициент)
expenses *= success.other.additive ?? 1;
// Вычитание скидки менеджера
expenses -= expenses * ((discount.value ?? 100) / 100);
// Округление
expenses = expenses.toFixed(2);
// Генерация текста ответа
result = expenses + ' рублей';
}
if (this.generate.result(result)) {
console.log(`[КАЛЬКУЛЯТОР] Сгенерирован результат: ${expenses} рублей`);
} else {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось сгенерировать результат');
}
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось сгенерировать результат');
});
}
});
},
generate: {
buyer(value = 'individual') {
// Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо)
return fetch('/calculator/generate/buyer?value=' + value, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора типа покупателя');
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора типа покупателя');
});
}
});
},
complexity(value = 'medium') {
// Запрос и генерация HTML с данными о сложности работы
return fetch('/calculator/generate/complexity?value=' + value, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора сложности');
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора сложности');
});
}
});
},
menu() {
// Запрос и генерация HTML с кнопками добавления калькулятора
return fetch('/calculator/generate/menu', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками добавления калькулятора');
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками добавления калькулятора');
});
}
});
},
mark(element, type = '') {
// Запрос и генерация HTML с полем выбора марки металла
return fetch('/calculator/generate/mark?type=' + type, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
// Поиск устаревшего списка с марками
let old = element.querySelectorAll('select[name="mark"]')[0];
if (old !== undefined) {
// Найден список с марками
// Деинициализация
old.parentElement.parentElement.remove();
}
// Инициализация оболочки в которую необходимо записать список
let wrap = element.querySelectorAll('select[name="type"]')[0].parentElement.parentElement
// Запись полученного списка в оболочку
wrap.insertAdjacentHTML('afterend', success);
console.log('[КАЛЬКУЛЯТОР] Загружен список с марками металла');
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить список с марками металла');
});
}
});
},
divider(element, position) {
// Запрос и генерация HTML с данными о результате калькуляции
return fetch('/calculator/generate/divider', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
if (element === undefined || position === undefined) {
// Не задан элемент и позиция добавляемого разделителя
// Запись разделителя в конце калькулятора
calculator.index.insertAdjacentHTML('beforeend', success);
} else {
// Задан элемент и позиция добавляемого разделителя
// Запись разделителя по заданным параметрам
element.insertAdjacentHTML(position, success);
}
console.log('[КАЛЬКУЛЯТОР] Загружен разделитель');
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить разделитель');
});
}
});
},
result(expenses) {
// Запрос и генерация HTML с данными о результате калькуляции
function request() {
return fetch('/calculator/generate/result', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с данными о результате калькуляции');
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с данными о результате калькуляции');
});
}
});
}
if (document.getElementById("result") === null) {
// Не найден элемент с данными расчётов
} else {
// Найден элемент с данными расчётов
if (expenses !== undefined) {
// Переданы расходы
// Инициализация элемента
let element = document.getElementById('calculate');
if (element == null) {
// Не найден элемент с результатом расчёта
return false;
}
// Запись расходов в элемент (подразумевается кнопка отправки на расчёт)
element.innerText = expenses;
return true;
}
}
return request();
},
error(errors = []) {
// Генерация ошибки
// Инициализация количества обработанных ошибок
let amount = 0;
if (typeof errors === 'object') {
// Передан массив с ошибками и он является массивом
// Инициализация буфера для проверки вложенности массива
let first = Object.values(errors)[0];
if (first !== undefined && first.text === undefined) {
// Найден массив с ошибками (категория)
// Вход в рекурсию
amount += this.error(first);
} else {
// Не найден массив с ошибками (подразумевается, что это и есть информация об ошибке)
// Инициализация элемента-оболочки
let list = document.getElementById('errors');
// Перезапись данных об ошибках
list.innerText = '';
// Проверка на наличие ошибок
if (errors.length === 0) return false;
if (list !== null) {
// Оболочка найдена
for (const [, error] of Object.entries(errors)) {
// Перебор станков
// Инициализация элемента-заголовка
let term = document.createElement('dt');
// Запись содержимого
term.innerText = error.text;
// Инициализация элемента-описания
let definition = document.createElement('dd');
// Запись содержимого
definition.innerText = error.file + ' в строке ' + error.line;
// input.setAttribute('id', element.id);
// Запись в список
list.insertAdjacentElement('beforeend', term);
list.insertAdjacentElement('beforeend', definition);
// Добавление к счётчику обработанных ошибок
++amount;
}
}
}
}
return amount;
},
calculators: {
laser() {
// Запрос и генерация HTML с калькулятором лазерной резки
function write(target, position, html) {
if (target === undefined || position === undefined || html === undefined) return false;
// Запись калькулятора после последнего калькулятора
target.insertAdjacentHTML(position, html);
// Поиск калькуляторов
let calculators = calculator.index.querySelectorAll('section[data-calculator]');
// Инициализация идентификатора калькулятора
let id = calculators.length - 1;
// Запись калькулятору его идентификатора
calculators[id].id = 'laser_' + id;
// Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
calculator.calculators.push(calculators[id]);
// Запись в журнал
console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
// Инициализация поля с маркой металла
calculator.generate.mark(calculators[id]);
}
return fetch('/calculator/generate/laser', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
// Поиск последнего калькулятора
let last = calculator.calculators[calculator.calculators.length - 1];
if (last !== undefined && last !== null) {
// Найден калькулятор
// Инициализация разделителя перед меню
calculator.generate.divider(last, 'afterend').then(divider => write(last, 'afterend', success));
} else {
// Не найден калькулятор
calculator.generate.divider(menu, 'beforebegin').then(
first => {
// Поиск меню
let menu = document.getElementById("menu");
if (menu !== null) {
// Найдено меню
// Инициализация разделителя перед меню
calculator.generate.divider(menu, 'beforebegin').then(divider => write(menu, 'beforebegin', success));
} else {
// Не найдено меню
// Поиск результатов калькуляции
let result = document.getElementById("result");
if (result !== null) {
// Найден элемент с результатами калькуляции
// Инициализация разделителя перед меню
calculator.generate.divider(result, 'beforebegin').then(result => write(result, 'beforebegin', success));
} else {
// Не найден элемент с результатами калькуляции
// Инициализация разделителя перед меню
calculator.generate.divider().then(result => write(calculator.index, 'beforeend', success));
}
}
}
);
}
},
error => {
// Запись в журнал
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось инициализировать калькулятор лазерной резки');
});
}
});
}
}
}
};

View File

@ -1,10 +0,0 @@
<div>
<label>Марка</label>
<div>
<select name="mark" data-calculator-parameter="mark" title="Марка стали">
{% for mark in marks %}
<option value="{{ mark }}">{{ mark }}</option>
{% endfor %}
</select>
</div>
</div>

View File

@ -1,69 +0,0 @@
<h3>Лазерная резка <span title="Удалить"></span></h3>
<section class="calculator" data-calculator="laser">
<div>
<label>Тип</label>
<div>
<select name="type" data-calculator-parameter="type" title="Тип стали"
onchange="calculator.generate.mark(this.parentElement.parentElement.parentElement, this.value)">
<option value="steel">Сталь</option>
<option value="galvanized_steel">Оцинкованная сталь</option>
<option value="stainless_steel">Нержавеющая сталь</option>
<option value="brass">Латунь</option>
<option value="copper">Медь</option>
<option value="aluminum">Алюминий</option>
</select>
</div>
</div>
<div>
<label>Размер</label>
<div>
<input data-calculator-parameter="width" type="number" class="measured" title="Длина детали"
value="{{ calculators.laser.width }}" min="{{ calculators.laser.width.min ?? 1 }}"
max="{{ calculators.laser.width.max ?? 3000 }}">
<span class="unit unselectable">мм</span>
<small>x</small>
<input data-calculator-parameter="height" type="number" class="measured" title="Ширина детали"
value="{{ calculators.laser.height }}" min="{{ calculators.laser.height.min ?? 1 }}"
max="{{ calculators.laser.height.max ?? 3000 }}">
<span class="unit unselectable">мм</span>
<small>x</small>
<input data-calculator-parameter="length" type="number" class="measured" title="Толщина детали"
value="{{ calculators.laser.length }}" min="{{ calculators.laser.length.min ?? 1 }}"
max="{{ calculators.laser.length.max ?? 20 }}">
<span class="unit unselectable">мм</span>
</div>
</div>
<div>
<label>Отверстия</label>
<div>
<input data-calculator-parameter="holes" type="number" class="measured" title="Количество отверстий"
value="{{ calculators.laser.holes }}" min="{{ calculators.laser.holes.min ?? 0 }}"
max="{{ calculators.laser.holes.max ?? 100 }}">
<span class="unit unselectable">шт</span>
<small>x</small>
<input data-calculator-parameter="diameter" type="number" class="measured" title="Диаметр отверстий"
value="{{ calculators.laser.diameter }}" min="{{ calculators.laser.diameter.min ?? 0 }}"
max="{{ calculators.laser.diameter.max ?? 100 }}">
<span class="unit unselectable">мм</span>
</div>
</div>
<div>
<label>Количество</label>
<div>
<input data-calculator-parameter="amount" type="number" class="measured" title="Количество изделий"
value="{{ calculators.laser.amount }}" min="{{ calculators.laser.amount.min ?? 1 }}"
max="{{ calculators.laser.amount.max ?? 10000 }}">
<span class="unit unselectable">шт</span>
</div>
</div>
<div>
<label for="our">Наш металл</label>
<div>
<input data-calculator-parameter="our" type="checkbox" title="Используется наш металл" checked>
</div>
</div>
</section>

View File

@ -1,18 +0,0 @@
<section id="menu" class="unselectable">
<a type="button" onclick="calculator.generate.calculators.laser(); return false;">
<img src="/img/laser.png" title="Добавить лазерную резку">
Лазерная резка
</a>
<a type="button" onclick="calculator.generate.calculators.plasma(); return false;">
<img src="/img/plasma.png" title="Добавить плазменную резку">
Плазменная резка
</a>
<a type="button" onclick="calculator.generate.calculators.bending(); return false;">
<img src="/img/bending.png" title="Добавить гибку металла">
Гибка металла
</a>
<a type="button" onclick="calculator.generate.calculators.painting(); return false;">
<img src="/img/painting.png" title="Добавить порошковую покраску">
Порошковая покраска
</a>
</section>

View File

@ -2,16 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\controllers\core; use mirzaev\zkmr\calculator\controllers\core;
use mirzaev\tordv\calculator\models\accounts_model as accounts; use mirzaev\zkmr\calculator\models\accounts_model as accounts;
/** /**
* Контроллер пользователей * Контроллер пользователей
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class accounts_controller extends core final class accounts_controller extends core

View File

@ -0,0 +1,307 @@
<?php
declare(strict_types=1);
namespace mirzaev\zkmr\calculator\controllers;
use Exception;
use mirzaev\zkmr\calculator\controllers\core;
use mirzaev\zkmr\calculator\models\calculators_model as calculators;
use mirzaev\zkmr\calculator\models\settings_model as settings;
use mirzaev\zkmr\calculator\models\fences_model as fences;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
/**
* Контроллер основной страницы
*
* @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class calculator_controller extends core
{
/**
* Калькулятор
*
* HTML-код с калькулятором
*
* @param array $vars Параметры
*/
public function index(array $vars = []): ?string
{
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculator' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
/**
* Модуль: "тип пользователя"
*
* HTML-код с кнопками: "физическое лицо" и "юридическое лицо"
*
* @param array $vars Параметры
*/
public function buyer(array $vars = []): ?string
{
// Инициализация параметров
$vars['buyer'] = $vars['value'] ?? 'individual';
// Удаление параметров
unset($vars['value']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'buyer.html', $vars);
}
/**
* Модуль: "сложность"
*
* HTML-код с кнопками: "легко", "средне" и "сложно"
*
* @param array $vars Параметры
*/
public function complexity(array $vars = []): ?string
{
// Инициализация параметров
$vars['complexity'] = $vars['value'] ?? 'medium';
// Удаление параметров
unset($vars['value']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'complexity.html', $vars);
}
/**
* Модуль: "меню"
*
* HTML-код с кнопками добавления калькуляторов
*
* @param array $vars Параметры
*/
public function menu(array $vars = []): ?string
{
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'menu.html', $vars);
}
/**
* Модуль: "результат"
*
* HTML-код с данными результата калькуляции
*
* @param array $vars Параметры
*/
public function result(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация данных калькулятора
$vars['discount'] = settings::read('discount', $vars['errors']['calculators']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'result.html', $vars);
}
/**
* Модуль: "тип забора"
*
* HTML-код со списком типов забора
*
* @param array $vars Параметры
*/
public function fence(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'fences' . DIRECTORY_SEPARATOR . $vars['type'] . '.html', $vars);
}
/**
* Модуль: "разделитель"
*
* HTML-код с разделителем элементов
*
* @param array $vars Параметры
*/
public function divider(array $vars = []): ?string
{
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'divider.html', $vars);
}
/**
* Профнастил
*
* HTML-код с калькулятором профнастила
*
* @param array $vars Параметры
*/
public function profnastil(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация данных калькулятора
$vars['calculators'] = ['profnastil' => [
'width' => (int) round((float) settings::read('default_width', $vars['errors']['calculators'])),
'height' => (int) round((float) settings::read('default_height', $vars['errors']['calculators'])),
'length' => (int) round((float) settings::read('default_length', $vars['errors']['calculators'])),
'amount' => (int) round((float) settings::read('default_amount', $vars['errors']['calculators'])),
'type' => settings::read('default_type', $vars['errors']['calculators']),
'holes' => (int) round((float) settings::read('default_holes', $vars['errors']['calculators'])),
'diameter' => (float) settings::read('default_diameter', $vars['errors']['calculators'])
]];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'profnastil.html', $vars);
}
/**
* 3D-сетка
*
* HTML-код с калькулятором 3D-сетки
*
* @param array $vars Параметры
*/
public function setka(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация данных калькулятора
$vars['calculators'] = ['profnastil' => [
'width' => (int) round((float) settings::read('default_width', $vars['errors']['calculators'])),
'height' => (int) round((float) settings::read('default_height', $vars['errors']['calculators'])),
'length' => (int) round((float) settings::read('default_length', $vars['errors']['calculators'])),
'amount' => (int) round((float) settings::read('default_amount', $vars['errors']['calculators'])),
'type' => settings::read('default_type', $vars['errors']['calculators']),
'holes' => (int) round((float) settings::read('default_holes', $vars['errors']['calculators'])),
'diameter' => (float) settings::read('default_diameter', $vars['errors']['calculators'])
]];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'setka.html', $vars);
}
/**
* Евроштакет
*
* HTML-код с калькулятором евроштакета
*
* @param array $vars Параметры
*/
public function evroshtaket(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация данных калькулятора
$vars['calculators'] = ['profnastil' => [
'width' => (int) round((float) settings::read('default_width', $vars['errors']['calculators'])),
'height' => (int) round((float) settings::read('default_height', $vars['errors']['calculators'])),
'length' => (int) round((float) settings::read('default_length', $vars['errors']['calculators'])),
'amount' => (int) round((float) settings::read('default_amount', $vars['errors']['calculators'])),
'type' => settings::read('default_type', $vars['errors']['calculators']),
'holes' => (int) round((float) settings::read('default_holes', $vars['errors']['calculators'])),
'diameter' => (float) settings::read('default_diameter', $vars['errors']['calculators'])
]];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'evroshtaket.html', $vars);
}
/**
* Расчёт
*
* Генерирует ответ в виде ['expenses' => 0, 'income' => 0, 'profit' => 0]
*
* @param array $vars Параметры
*
* @todo
* 5. Убрать передачу цены работы (оставить только время работы в часах и цену за работу в час)
*/
public function calculate(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
try {
// Инициализация параметров из тела запроса (подразумевается, что там массивы с параметрами)
$vars['input'] = json_decode(file_get_contents('php://input'), true);
$calculators = $vars['input']['calculators'];
$discount = $vars['input']['discount'];
$cutting = $vars['input']['cutting'];
// Инициализация переменных для буфера вывода
$machines = $managers = $engineers = $operators = $handymans = $other = 0;
if (count($calculators) > 0) {
// Найдены калькуляторы
foreach ($calculators as $i => $calculator) {
// Перебор калькуляторов
foreach (['calculator'] as &$parameter) {
// Перебор мета-параметров
// Инициализация общего параметра
$type = $calculator[$parameter];
// Инициализация параметра для обработчика калькулятора
unset($calculator[$parameter]);
}
// Инициализация номера калькулятора в его категории
$number = count($vars['errors']['calculators'][$type] ?? []);
// Инициализация журнала ошибок для калькулятора
$calculator['errors'] = [];
// Инициализация журнала ошибок для буфера вывода
$vars['errors']['calculators'][$type][$number] = &$calculator['errors'];
// Инициализация буфера параметров
$parameters = [];
// Инициализация параметра типа покупателя (подразумевается, что если не "entity", то "individual")
$parameters['company'] = $calculator['buyer'] === 'entity';
unset($calculator['buyer']);
// Перенос остальных параметров в буфер параметров
$parameters += $calculator;
// Расчёт
[$machines, $managers, $engineers, $operators, $handymans, $other] = calculators::$type(...$parameters + ['cutting' => $cutting]);
}
} else {
// Не найдены калькуляторы
throw new exception('Не найдены калькуляторы');
}
} catch (exception $e) {
// Запись в журнал ошибок
$vars['errors']['calculators'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return json_encode([
'machines' => $machines,
'managers' => $managers,
'engineers' => $engineers,
'operators' => $operators,
'handymans' => $handymans,
'other' => $other + ['discount' => $discount],
'errors' => $vars['errors']
]);
}
}

View File

@ -2,15 +2,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\controllers\core; use mirzaev\zkmr\calculator\controllers\core;
use mirzaev\tordv\calculator\models\accounts_model as accounts; use mirzaev\zkmr\calculator\models\accounts_model as accounts;
/** /**
* Контроллер контактов * Контроллер контактов
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class contacts_controller extends core final class contacts_controller extends core

View File

@ -2,17 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\views\manager; use mirzaev\zkmr\calculator\views\manager;
use mirzaev\tordv\calculator\models\core as models; use mirzaev\zkmr\calculator\models\core as models;
use mirzaev\minimal\controller; use mirzaev\minimal\controller;
/** /**
* Менеджер представлений * Менеджер представлений
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class core extends controller class core extends controller

View File

@ -2,9 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\controllers\core; use mirzaev\zkmr\calculator\controllers\core;
use Twig\Loader\FilesystemLoader; use Twig\Loader\FilesystemLoader;
use Twig\Environment as view; use Twig\Environment as view;
@ -12,7 +12,7 @@ use Twig\Environment as view;
/** /**
* Контроллер основной страницы * Контроллер основной страницы
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class errors_controller extends core final class errors_controller extends core

View File

@ -2,15 +2,15 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\controllers\core; use mirzaev\zkmr\calculator\controllers\core;
use mirzaev\tordv\calculator\models\accounts_model as accounts; use mirzaev\zkmr\calculator\models\accounts_model as accounts;
/** /**
* Контроллер журналов * Контроллер журналов
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class journal_controller extends core final class journal_controller extends core

View File

@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\controllers\core; use mirzaev\zkmr\calculator\controllers\core;
use mirzaev\tordv\calculator\models\accounts_model as accounts; use mirzaev\zkmr\calculator\models\accounts_model as accounts;
use Twig\Loader\FilesystemLoader; use Twig\Loader\FilesystemLoader;
use Twig\Environment as view; use Twig\Environment as view;
@ -13,7 +13,7 @@ use Twig\Environment as view;
/** /**
* Контроллер основной страницы * Контроллер основной страницы
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class main_controller extends core final class main_controller extends core

View File

@ -2,16 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\controllers\core; use mirzaev\zkmr\calculator\controllers\core;
use mirzaev\tordv\calculator\models\accounts_model as accounts; use mirzaev\zkmr\calculator\models\accounts_model as accounts;
use mirzaev\tordv\calculator\models\settings_model as settings; use mirzaev\zkmr\calculator\models\settings_model as settings;
/** /**
* Контроллер настроек * Контроллер настроек
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class settings_controller extends core final class settings_controller extends core

View File

@ -2,25 +2,25 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\controllers; namespace mirzaev\zkmr\calculator\controllers;
use mirzaev\tordv\calculator\controllers\core; use mirzaev\zkmr\calculator\controllers\core;
use mirzaev\tordv\calculator\models\accounts_model as accounts; use mirzaev\zkmr\calculator\models\accounts_model as accounts;
use mirzaev\tordv\calculator\models\supplies_model as supplies; use mirzaev\zkmr\calculator\models\supplies_model as supplies;
use mirzaev\tordv\calculator\models\filters\import_aluminum_filter; use mirzaev\zkmr\calculator\models\filters\import_aluminum_filter;
use mirzaev\tordv\calculator\models\filters\import_brass_filter; use mirzaev\zkmr\calculator\models\filters\import_brass_filter;
use mirzaev\tordv\calculator\models\filters\import_copper_filter; use mirzaev\zkmr\calculator\models\filters\import_copper_filter;
use mirzaev\tordv\calculator\models\filters\import_stainless_steel_filter; use mirzaev\zkmr\calculator\models\filters\import_stainless_steel_filter;
use mirzaev\tordv\calculator\models\filters\import_steel_filter; use mirzaev\zkmr\calculator\models\filters\import_steel_filter;
use mirzaev\tordv\calculator\models\filters\import_galvanized_steel_filter; use mirzaev\zkmr\calculator\models\filters\import_galvanized_steel_filter;
use Exception; use Exception;
/** /**
* Контроллер поставок * Контроллер поставок
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class supplies_controller extends core final class supplies_controller extends core

View File

@ -0,0 +1,621 @@
<?php
declare(strict_types=1);
namespace mirzaev\zkmr\calculator\models;
use pdo;
use exception;
/**
* Модель регистрации, аутентификации и авторизации
*
* @package mirzaev\zkmr\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class accounts_model extends core
{
/**
* Регистрация
*
* @param string $name Входной псевдоним
* @param string $email Почта
* @param string $password Пароль (password)
* @param bool $authentication Автоматическая аутентификация в случае успешной регистрации
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт, если удалось аутентифицироваться
*/
public static function registration(string $name = null, string $email = null, string $password, array &$errors = []): array
{
try {
if (static::account($errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $name]) or $account = static::read(['email' => $email]))) {
// Не удалось найти аккаунт
if (static::write($name, $email, $password, $errors)) {
// Удалось зарегистрироваться
return $account;
}
} else {
// Удалось найти аккаунт
return $account;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Аутентификация
*
* @param string $login Входной псевдоним
* @param string $password Пароль (password)
* @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*/
public static function authentication(string $login, string $password, bool $remember = false, array &$errors = []): array
{
try {
if (static::account($errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $login]) or $account = static::read(['email' => $login]))) {
// Не удалось найти аккаунт
throw new exception('Не удалось найти аккаунт');
}
if (password_verify($password, $account['password'])) {
// Совпадают хеши паролей
// Инициализация идентификатора сессии
session_id($account['id']);
// Инициализация названия сессии
session_name('id');
// Инициализация сессии
session_start();
// Инициализация времени хранения хеша
$time = time() + ($remember ? 604800 : 86400);
// Инициализация хеша
$hash = static::hash((int) $account['id'], crypt($account['password'], time() . $account['id']), $time, $errors)['hash'];
// Инициализация cookies
setcookie("hash", $hash, $time, path: '/', secure: true);
return $account;
} else {
// Не совпадают хеши паролей
throw new exception('Неправильный пароль');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Аутентификация
*
* @param array &$errors Журнал ошибок
*
* @return bool Удалось ли деаутентифицироваться
*/
public static function deauthentication(array &$errors = []): bool
{
try {
if ($account = static::account($errors)) {
// Аутентифицирован пользователь
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = null, `time` = 0 WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $account['id'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
// Деинициализация cookies
setcookie("id", '', 0, path: '/', secure: true);
setcookie("hash", '', 0, path: '/', secure: true);
return true;
} else {
// Не аутентифицирован пользователь
// Запись ошибки
throw new exception('Не аутентифицирован');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Прочитать данные аккаунта, если пользователь аутентифицирован
*
* Можно использовать как проверку на аутентифицированность
*
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*
* @todo 1. Сделать в static::read() возможность передачи нескольких параметров и перенести туда непосредственно чтение аккаунта с проверкой хеша
*/
public static function account(array &$errors = []): array
{
try {
if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
// Аутентифицирован аккаунт (найдены cookie и они хранят значения - подразумевается, что не null или пустое)
if ($_COOKIE['hash'] === static::hash((int) $_COOKIE['id'], errors: $errors)['hash']) {
// Совпадает переданный хеш с тем, что хранится в базе данных
} else {
// Не совпадает переданный хеш с тем, что хранится в базе данных
// Генерация ошибки
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `id` = :id && `hash` = :hash");
// Параметры запроса
$params = [
":id" => $_COOKIE['id'],
":hash" => $_COOKIE['hash'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($account = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдена связка идентификатора с хешем
// Генерация ошибки
throw new exception('Не найден пользотватель или время аутентификации истекло');
}
// Чтение разрешений
$account['permissions'] = static::permissions((int) $account['id'], $errors);
return $account;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Прочитать разрешения аккаунта
*
* @param int $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
*
* @return array Разрешения аккаунта, если найдены
*/
public static function permissions(int $id, array &$errors = []): array
{
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($response = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдены разрешения
// Генерация ошибки
throw new exception('Не найдены разрешения');
}
// Удаление ненужных данных
unset($response['id']);
return $response;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return [];
}
/**
* Проверить разрешение
*
* @param string $permission Разрешение
* @param int|null $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
*
* @return bool|null Статус разрешения, если оно записано
*/
public static function access(string $permission, int|null $id = null, array &$errors = []): ?bool
{
try {
// Инициализация аккаунта
$account = isset($id) ? self::read(['id' => $id], $errors) : self::account($errors);
return isset($account['permissions'][$permission]) ? (bool) $account['permissions'][$permission] : null;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Запись пользователя в базу данных
*
* @param string|null $name Имя
* @param string|null $email Почта
* @param string|null $password Пароль
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*/
public static function write(string|null $name = null, string|null $email = null, string|null $password = null, array &$errors = []): array
{
try {
// Инициализация параметров запроса
$params = [];
if (isset($name)) {
try {
// Проверка параметра
if (iconv_strlen($name) < 3) throw new exception('Длина имени должна быть не менее 3 символов');
if (iconv_strlen($name) > 60) throw new exception('Длина имени должна быть не более 60 символов');
// Запись в буфер параметров запроса
$params[':name'] = $name;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($email)) {
try {
// Проверка параметра
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту');
if (iconv_strlen($email) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($email) > 60) throw new exception('Длина почты должна быть не более 80 символов');
// Запись в буфер параметров запроса
$params[':email'] = $email;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($password)) {
try {
// Проверка параметра
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
// Запись в буфер параметров запроса
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? '`email`' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? ':email' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
try {
if (isset($name)) {
// Передано имя аккаунта
// Чтение аккаунта
$account = static::read(['name' => $name]);
} else if (isset($email)) {
// Передана почта аккаунта
// Чтение аккаунта
$account = static::read(['email' => $email]);
} else {
// Не передано ни имя, ни почта
throw new exception('Не переданны данные для полноценной регистрации аккаунта');
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `permissions` (`id`) VALUES (:id)");
// Инициализация параметров
$params = [
':id' => $account['id']
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Конец выполнения
end:
return isset($account) && $account ? $account : [];
}
/**
* Чтение пользователя из базы данных
*
* @param array $search Поиск ('поле' => 'значение'), работает только с одним полем
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт, если найден
*/
public static function read(array $search, array &$errors = []): array
{
try {
// Инициализация данных для поиска
$field = array_keys($search)[0] ?? null;
$value = $search[$field] ?? null;
if (empty($field)) {
// Получено пустое значение поля
// Запись ошибки
throw new exception('Пустое значение поля для поиска');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `$field` = :field LIMIT 1");
// Параметры запроса
$params = [
":field" => $value,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if ($account = $request->fetch(pdo::FETCH_ASSOC)) {
// Найден аккаунт
try {
if ($permissions = static::permissions((int) $account['id'], $errors)) {
// Найдены разрешения
// Запись в буфер данных аккаунта
$account['permissions'] = $permissions;
} else {
// Не найдены разрешения
throw new exception('Не удалось найти и прочитать разрешения');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
}
} else {
// Не найден аккаунт
throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return isset($account) && $account ? $account : [];
}
/**
* Запись или чтение хеша из базы данных
*
* @param int $id Идентификатор аккаунта
* @param int|null $hash Хеш аутентифиакции
* @param string|null $time Время хранения хеша
* @param array &$errors Журнал ошибок
*
* @return array ['hash' => $hash, 'time' => $time]
*/
public static function hash(int $id, string|null $hash = null, int|null $time = null, array &$errors = []): array
{
try {
if (isset($hash, $time)) {
// Переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => $hash,
":time" => $time,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} else {
// Не переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("SELECT `hash`, `time` FROM `accounts` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
extract((array) $request->fetch(pdo::FETCH_ASSOC));
if (!empty($response['time']) && $response['time'] <= time()) {
// Истекло время жизни хеша
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => null,
":time" => null,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$response = $request->fetch(pdo::FETCH_ASSOC);
// Генерация ошибки
throw new exception('Время аутентификации истекло');
}
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return ['hash' => $hash, 'time' => $time];
}
}

View File

@ -2,18 +2,18 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models; namespace mirzaev\zkmr\calculator\models;
use mirzaev\tordv\calculator\models\settings_model as settings; use mirzaev\zkmr\calculator\models\settings_model as settings;
use mirzaev\tordv\calculator\models\metals_model as metals; use mirzaev\zkmr\calculator\models\metals_model as metals;
use mirzaev\tordv\calculator\models\baloons_model as baloons; use mirzaev\zkmr\calculator\models\baloons_model as baloons;
use exception; use exception;
/** /**
* Модель калькуляторов * Модель калькуляторов
* *
* @package mirzaev\tordv\calculator\models * @package mirzaev\zkmr\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class calculators_model extends core final class calculators_model extends core

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models; namespace mirzaev\zkmr\calculator\models;
use mirzaev\minimal\model; use mirzaev\minimal\model;
@ -14,7 +14,7 @@ use exception;
/** /**
* Ядро моделей * Ядро моделей
* *
* @package mirzaev\tordv\calculator\models * @package mirzaev\zkmr\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class core extends model class core extends model

View File

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter_noname_1; use mirzaev\zkmr\calculator\models\filters\import_filter_noname_1;
/** /**
* Фильтр импорта цен на алюминий от неизвестного поставщика под номером 1 * Фильтр импорта цен на алюминий от неизвестного поставщика под номером 1
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class import_aluminum_filter extends import_filter_noname_1 final class import_aluminum_filter extends import_filter_noname_1

View File

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter_noname_1; use mirzaev\zkmr\calculator\models\filters\import_filter_noname_1;
/** /**
* Фильтр импорта цен на латунь от неизвестного поставщика под номером 1 * Фильтр импорта цен на латунь от неизвестного поставщика под номером 1
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class import_brass_filter extends import_filter_noname_1 final class import_brass_filter extends import_filter_noname_1

View File

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter_noname_1; use mirzaev\zkmr\calculator\models\filters\import_filter_noname_1;
/** /**
* Фильтр импорта цен на медь от неизвестного поставщика под номером 1 * Фильтр импорта цен на медь от неизвестного поставщика под номером 1
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class import_copper_filter extends import_filter_noname_1 final class import_copper_filter extends import_filter_noname_1

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter as filter; use PhpOffice\PhpSpreadsheet\Reader\IReadFilter as filter;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
@ -10,7 +10,7 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
/** /**
* Интерфейс фильтра импорта * Интерфейс фильтра импорта
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
interface import_filter extends filter interface import_filter extends filter

View File

@ -2,17 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter as filter; use mirzaev\zkmr\calculator\models\filters\import_filter as filter;
use mirzaev\tordv\calculator\models\metals_model as metals; use mirzaev\zkmr\calculator\models\metals_model as metals;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
/** /**
* Фильтр импорта для документов от "МЕТАЛЛСЕРВИС" * Фильтр импорта для документов от "МЕТАЛЛСЕРВИС"
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class import_filter_mc implements filter class import_filter_mc implements filter

View File

@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter as filter; use mirzaev\zkmr\calculator\models\filters\import_filter as filter;
use mirzaev\tordv\calculator\models\metals_model as metals; use mirzaev\zkmr\calculator\models\metals_model as metals;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
@ -13,7 +13,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
/** /**
* Фильтр импорта для документов от неизвестного поставщика под номером 1 * Фильтр импорта для документов от неизвестного поставщика под номером 1
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class import_filter_noname_1 implements filter class import_filter_noname_1 implements filter

View File

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter_noname_1; use mirzaev\zkmr\calculator\models\filters\import_filter_noname_1;
/** /**
* Фильтр импорта цен на оцинкованную сталь от "МЕТАЛЛСЕРВИС" * Фильтр импорта цен на оцинкованную сталь от "МЕТАЛЛСЕРВИС"
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
* *
* @todo 1. Доделать 982 до 992 строки * @todo 1. Доделать 982 до 992 строки

View File

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter_noname_1; use mirzaev\zkmr\calculator\models\filters\import_filter_noname_1;
/** /**
* Фильтр импорта цен на нержавеющую сталь от неизвестного поставщика под номером 1 * Фильтр импорта цен на нержавеющую сталь от неизвестного поставщика под номером 1
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
* *
* @todo 1. Доделать 982 до 992 строки * @todo 1. Доделать 982 до 992 строки

View File

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models\filters; namespace mirzaev\zkmr\calculator\models\filters;
use mirzaev\tordv\calculator\models\filters\import_filter_mc; use mirzaev\zkmr\calculator\models\filters\import_filter_mc;
/** /**
* Фильтр импорта цен на чёрную сталь от "МЕТАЛЛСЕРВИС" * Фильтр импорта цен на чёрную сталь от "МЕТАЛЛСЕРВИС"
* *
* @package mirzaev\tordv\calculator\models\filters * @package mirzaev\zkmr\calculator\models\filters
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class import_steel_filter extends import_filter_mc final class import_steel_filter extends import_filter_mc

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models; namespace mirzaev\zkmr\calculator\models;
use pdo; use pdo;
use exception; use exception;
@ -10,7 +10,7 @@ use exception;
/** /**
* Модель настроек * Модель настроек
* *
* @package mirzaev\tordv\calculator\models * @package mirzaev\zkmr\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class settings_model extends core final class settings_model extends core

View File

@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\models; namespace mirzaev\zkmr\calculator\models;
use mirzaev\tordv\calculator\models\accounts_model as accounts; use mirzaev\zkmr\calculator\models\accounts_model as accounts;
use mirzaev\tordv\calculator\models\filters\import_filter as filter; use mirzaev\zkmr\calculator\models\filters\import_filter as filter;
use PhpOffice\PhpSpreadsheet\Reader\Xls as reader; use PhpOffice\PhpSpreadsheet\Reader\Xls as reader;
@ -15,7 +15,7 @@ use exception;
/** /**
* Модель поставки * Модель поставки
* *
* @package mirzaev\tordv\calculator\models * @package mirzaev\zkmr\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class supplies_model extends core final class supplies_model extends core

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator; namespace mirzaev\zkmr\calculator;
use mirzaev\minimal\core; use mirzaev\minimal\core;
use mirzaev\minimal\router; use mirzaev\minimal\router;
@ -10,9 +10,9 @@ use mirzaev\minimal\router;
define('SUPPLIES', realpath('supplies')); define('SUPPLIES', realpath('supplies'));
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views')); define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
define('TYPE', 'mysql'); define('TYPE', 'mysql');
define('BASE', 'calculator'); define('BASE', 'zkmr-calculator');
define('HOST', '127.0.0.1'); define('HOST', 'localhost');
define('LOGIN', 'root'); define('LOGIN', '');
define('PASSWORD', ''); define('PASSWORD', '');
// Автозагрузка // Автозагрузка
@ -33,9 +33,9 @@ $router->write('/calculator/generate/buyer', 'calculator', 'buyer', 'POST');
$router->write('/calculator/generate/complexity', 'calculator', 'complexity', 'POST'); $router->write('/calculator/generate/complexity', 'calculator', 'complexity', 'POST');
$router->write('/calculator/generate/menu', 'calculator', 'menu', 'POST'); $router->write('/calculator/generate/menu', 'calculator', 'menu', 'POST');
$router->write('/calculator/generate/result', 'calculator', 'result', 'POST'); $router->write('/calculator/generate/result', 'calculator', 'result', 'POST');
$router->write('/calculator/generate/mark', 'calculator', 'mark', 'POST'); $router->write('/calculator/generate/fence', 'calculator', 'fence', 'POST');
$router->write('/calculator/generate/divider', 'calculator', 'divider', 'POST'); $router->write('/calculator/generate/divider', 'calculator', 'divider', 'POST');
$router->write('/calculator/generate/laser', 'calculator', 'laser', 'POST'); $router->write('/calculator/generate/profnastil', 'calculator', 'profnastil', 'POST');
$router->write('/calculator/calculate', 'calculator', 'calculate', 'POST'); $router->write('/calculator/calculate', 'calculator', 'calculate', 'POST');
$router->write('/settings', 'settings', 'index', 'GET'); $router->write('/settings', 'settings', 'index', 'GET');
$router->write('/settings/write', 'settings', 'write', 'POST'); $router->write('/settings/write', 'settings', 'write', 'POST');

View File

@ -0,0 +1,588 @@
"use strict";
const calculator = {
index: document.getElementById("calculator"),
calculators: [],
account: [],
settings: {
defaults: {
},
},
init() {
// Инициализация калькулятора
this.generate.menu()
.then(
() => {
this.authenticate(cookie.read("id"))
.then(
(success) => {
// Запись данных аккаунта
this.account = success;
if (
this.account !== undefined &&
typeof this.account === "object" &&
this.account.permissions !== undefined
) {
// Найден аккаунт
if (this.account.permissions.calculate == 1) {
// Разрешено использовать калькулятор
this.generate.result();
}
}
},
);
},
);
console.log("[КАЛЬКУЛЯТОР] Инициализирован");
},
async authenticate(id) {
// Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо)'
return await fetch("/account/data?id=" + id, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
}).then((response) => {
if (response.status === 200) {
return response.json().then(
(success) => {
console.log("[КАЛЬКУЛЯТОР] Загружены данные пользователя: " + id);
return success;
},
() => {
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить данные пользователя: " +
id,
);
},
);
}
});
},
async calculate() {
// Запрос и генерация HTML с данными о рассчете со всех калькуляторов
// Инициализация параметров
const cutting = document.getElementById("cutting");
const discount = document.getElementById("discount");
// Инициализация буфера запроса
const query = {
calculators: {},
cutting: +cutting.value ?? 0,
discount: +discount.value ?? 0,
};
for (const number in this.calculators) {
// Перебор калькуляторов
// Инициализация буфера запроса для нового калькулятора
query["calculators"][number] = {};
// Инициализация типа калькулятора
query["calculators"][number]["calculator"] = this.calculators[number]
.getAttribute("data-calculator");
for (const buyer of this.index.querySelectorAll('input[name="buyer"]')) {
// Перебор полей с параметрами типа заказчика
if (buyer.checked) {
// Найдено выбранное поле
// Запись в буфер запроса
query["calculators"][number]["buyer"] = buyer.value;
}
}
for (
const complexity of this.index.querySelectorAll(
'input[name="complexity"]',
)
) {
// Перебор полей с параметрами сложности
if (complexity.checked) {
// Найдено выбранное поле
// Запись в буфер запроса
query["calculators"][number]["complexity"] = complexity.value;
}
}
for (
const field of this.calculators[number].querySelectorAll(
"[data-calculator-parameter]",
)
) {
// Перебор полей с параметрами
if (field.getAttribute("type") === "checkbox") {
// Флажок
// Запись в буфер запроса
query["calculators"][number][
field.getAttribute("data-calculator-parameter")
] = field.checked;
} else if (
field.getAttribute("type") === "text" ||
field.getAttribute("type") === "number" ||
field.getAttribute("type") === "range"
) {
// Текстовое, цифровое поле или ползунок
// Запись в буфер запроса
query["calculators"][number][
field.getAttribute("data-calculator-parameter")
] = field.value;
} else {
// Элемент с тегом <select> (подразумевается)
// Запись в буфер запроса
query["calculators"][number][
field.getAttribute("data-calculator-parameter")
] = field.value ?? field.options[field.selectedIndex].text;
}
}
}
return await fetch("/calculator/calculate", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(query),
}).then((response) => {
if (response.status === 200) {
response.json().then(
(success) => {
// Инициализация буфера расходов
let expenses = 0;
// Инициализация буфера с данными расчёта
let result;
if (this.generate.error(success.errors) > 0) {
// Найдены ошибки
// Генерация текста ответа
result = "Ошибка";
} else {
// Не найдены ошибки
if (success.other.cutting !== undefined) {
// Получены данные времени работы
// Запись полученных данных
cutting.value = success.other.cutting;
cutting.parentElement.children[0].innerText =
"Длина реза 1 детали (" + cutting.value + "мм)";
// Разблокировка параметра
cutting.removeAttribute("disabled");
}
if (success.other.discount !== undefined) {
// Получены данные скидки
// Запись полученных данных
discount.value = success.other.discount;
discount.parentElement.children[0].innerText = "Скидка (" +
discount.value + "%)";
}
for (const [, machine] of Object.entries(success.machines)) {
// Перебор станков
// Прибавление данных станка к буферу расходов
expenses += machine.electricity + (machine.metal ?? 0);
// Прибавление амортизации к буферу вывода
expenses += machine.reprocessing ?? 0;
// Прибавление линз к буферу вывода
expenses += machine.lenses ?? 0;
}
for (const [, manager] of Object.entries(success.managers)) {
// Перебор менеджеров
// Прибавление данных менеджера к буферу расходов
expenses += manager.time * manager.hour;
}
for (const [, engineer] of Object.entries(success.engineers)) {
// Перебор инженеров
// Прибавление данных инженера к буферу расходов
expenses += engineer.time * engineer.hour;
}
for (const [, operator] of Object.entries(success.operators)) {
// Перебор операторов
// Прибавление данных оператора к буферу расходов
expenses += (operator.time.design + operator.time.machine) *
operator.hour;
}
for (const [, handyman] of Object.entries(success.handymans)) {
// Перебор разнорабочих
// Прибавление данных к буферу расходов
expenses += handyman.time * handyman.hour;
}
// Прибавление аренды к буферу расходов
expenses += success.other.rent ?? 0;
// Прибавление переработки к буферу расходов
expenses += success.other.reprocessing ?? 0;
// Прибавление баллонов к буферу расходов
expenses += success.other.baloons.cost ?? 0;
// Вычисление наценки (коэффициент)
expenses *= success.other.additive ?? 1;
// Вычитание скидки менеджера
expenses -= expenses * ((discount.value ?? 100) / 100);
// Округление
expenses = expenses.toFixed(2);
// Генерация текста ответа
result = expenses + " рублей";
}
if (this.generate.result(result)) {
console.log(
`[КАЛЬКУЛЯТОР] Сгенерирован результат: ${expenses} рублей`,
);
} else {
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось сгенерировать результат",
);
}
},
() => {
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось сгенерировать результат",
);
},
);
}
});
},
generate: {
async menu() {
// Запрос и генерация HTML с кнопками добавления калькулятора
return await fetch("/calculator/generate/menu", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
}).then((response) => {
if (response.status === 200) {
response.text().then(
(success) => {
calculator.index.insertAdjacentHTML("beforeend", success);
console.log(
"[КАЛЬКУЛЯТОР] Загружен элемент с кнопками добавления калькулятора",
);
},
() => {
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками добавления калькулятора",
);
},
);
}
});
},
async fence(element, type = 1) {
// Генерация полей с выбором типа забора
return await fetch("/calculator/generate/fence?type=" + type, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
}).then((response) => {
if (response.status === 200) {
response.text().then(
(success) => {
// Поиск оболочки списка
const wrap = element.querySelectorAll('select[name="fence"]')[0];
// Запись полученного списка в оболочку
wrap.parentElement.parentElement.insertAdjacentHTML(
"afterend",
success,
);
// Удаление старого списка
if (wrap !== undefined) wrap.parentElement.parentElement.remove();
console.log("[КАЛЬКУЛЯТОР] Загружен список с типами забора");
},
() => {
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить список с типами забора",
);
},
);
}
});
},
async divider(element, position) {
// Запрос и генерация HTML с данными о результате калькуляции
return await fetch("/calculator/generate/divider", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
}).then((response) => {
if (response.status === 200) {
response.text().then(
(success) => {
if (element === undefined || position === undefined) {
// Не задан элемент и позиция добавляемого разделителя
// Запись разделителя в конце калькулятора
calculator.index.insertAdjacentHTML("beforeend", success);
} else {
// Задан элемент и позиция добавляемого разделителя
// Запись разделителя по заданным параметрам
element.insertAdjacentHTML(position, success);
}
console.log("[КАЛЬКУЛЯТОР] Загружен разделитель");
},
() => {
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить разделитель",
);
},
);
}
});
},
result(expenses) {
// Запрос и генерация HTML с данными о результате калькуляции
async function request() {
return await fetch("/calculator/generate/result", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
}).then((response) => {
if (response.status === 200) {
response.text().then(
(success) => {
calculator.index.insertAdjacentHTML("beforeend", success);
console.log(
"[КАЛЬКУЛЯТОР] Загружен элемент с данными о результате калькуляции",
);
},
() => {
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с данными о результате калькуляции",
);
},
);
}
});
}
if (document.getElementById("result") === null) {
// Не найден элемент с данными расчётов
} else {
// Найден элемент с данными расчётов
if (expenses !== undefined) {
// Переданы расходы
// Инициализация элемента
const element = document.getElementById("calculate");
if (element == null) {
// Не найден элемент с результатом расчёта
return false;
}
// Запись расходов в элемент (подразумевается кнопка отправки на расчёт)
element.innerText = expenses;
return true;
}
}
return request();
},
error(errors = []) {
// Генерация ошибки
// Инициализация количества обработанных ошибок
let amount = 0;
if (typeof errors === "object") {
// Передан массив с ошибками и он является массивом
// Инициализация буфера для проверки вложенности массива
const first = Object.values(errors)[0];
if (first !== undefined && first.text === undefined) {
// Найден массив с ошибками (категория)
// Вход в рекурсию
amount += this.error(first);
} else {
// Не найден массив с ошибками (подразумевается, что это и есть информация об ошибке)
// Инициализация элемента-оболочки
const list = document.getElementById("errors");
// Перезапись данных об ошибках
list.innerText = "";
// Проверка на наличие ошибок
if (errors.length === 0) return false;
if (list !== null) {
// Оболочка найдена
for (const [, error] of Object.entries(errors)) {
// Перебор станков
// Инициализация элемента-заголовка
const term = document.createElement("dt");
// Запись содержимого
term.innerText = error.text;
// Инициализация элемента-описания
const definition = document.createElement("dd");
// Запись содержимого
definition.innerText = error.file + " в строке " + error.line;
// input.setAttribute('id', element.id);
// Запись в список
list.insertAdjacentElement("beforeend", term);
list.insertAdjacentElement("beforeend", definition);
// Добавление к счётчику обработанных ошибок
++amount;
}
}
}
}
return amount;
},
calculators: {
async profnastil() {
// Запрос и генерация HTML с калькулятором лазерной резки
function write(target, position, html) {
if (
target === undefined || position === undefined || html === undefined
) return false;
// Запись калькулятора после последнего калькулятора
target.insertAdjacentHTML(position, html);
// Поиск калькуляторов
const calculators = calculator.index.querySelectorAll(
"section[data-calculator]",
);
// Инициализация идентификатора калькулятора
const id = calculators.length - 1;
// Запись калькулятору его идентификатора
calculators[id].id = "profnastil_" + id;
// Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
calculator.calculators.push(calculators[id]);
// Запись в журнал
console.log(
"[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки",
);
}
return await fetch("/calculator/generate/profnastil", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
}).then((response) => {
if (response.status === 200) {
response.text().then(
(success) => {
// Поиск последнего калькулятора
const last =
calculator.calculators[calculator.calculators.length - 1];
if (last !== undefined && last !== null) {
// Найден калькулятор
// Инициализация разделителя перед калькулятором
calculator.generate.divider(last, "afterend").then(
() => write(last, "afterend", success),
);
} else {
// Не найден калькулятор
calculator.generate.divider(menu, "beforebegin").then(
() => {
// Поиск меню
const menu = document.getElementById("menu");
if (menu !== null) {
// Найдено меню
// Инициализация разделителя перед меню
write(menu, "beforebegin", success)
} else {
// Не найдено меню
// Поиск результатов калькуляции
const result = document.getElementById("result");
if (result !== null) {
// Найден элемент с результатами калькуляции
// Инициализация разделителя перед меню
calculator.generate.divider(result, "beforebegin")
.then((result) =>
write(result, "beforebegin", success)
);
} else {
// Не найден элемент с результатами калькуляции
// Инициализация разделителя перед меню
calculator.generate.divider().then(() =>
write(calculator.index, "beforeend", success)
);
}
}
},
);
}
},
() => {
// Запись в журнал
console.log(
"[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось инициализировать калькулятор лазерной резки",
);
},
);
}
});
},
},
},
};

View File

@ -0,0 +1,8 @@
<div>
<label>Забор</label>
<div>
<select name="fence" data-calculator-parameter="fence" title="Тип забора">
<option value="1">???</option>
</select>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div>
<label>Забор</label>
<div>
<select name="fence" data-calculator-parameter="fence" title="Тип забора">
<option value="1">Сплошной</option>
<option value="2">Секционный</option>
</select>
</div>
</div>

View File

@ -16,18 +16,18 @@
calculator.init(); calculator.init();
} }
} else { } else {
// Документ не загружен // Документ не загружен
// Обработчик события загрузки документа // Обработчик события загрузки документа
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// Обработчик события инициализации // Обработчик события инициализации
if (calculator !== undefined) { if (calculator !== undefined) {
// Калькулятор инициализирован // Калькулятор инициализирован
calculator.init(); calculator.init();
} }
}, false); }, false);
}; };
</script> </script>

View File

@ -0,0 +1,14 @@
<section id="menu" class="unselectable">
<a type="button" onclick="calculator.generate.calculators.profnastil(); return false;">
<img src="" title="Расчитать профнастил">
Профнастил
</a>
<a type="button" onclick="calculator.generate.calculators.setka(); return false;">
<img src="" title="Расчитать 3D-сетку">
3D-сетка
</a>
<a type="button" onclick="calculator.generate.calculators.evroshtaket); return false;">
<img src="" title="Расчитать евроштакет">
Евроштакет
</a>
</section>

View File

@ -0,0 +1,97 @@
<h3>Забор из профнастила<span title="Удалить"></span></h3>
<section class="calculator" data-calculator="laser">
<div>
<label>Тип</label>
<div>
<select name="type" data-calculator-parameter="type" title="Выбор типа профнастила">
<option value="c8">C8</option>
<option value="c10">C10</option>
<option value="hc21">HC21</option>
</select>
</div>
</div>
<div>
<label>Цвет каркаса</label>
<div>
<select name="base_color" data-calculator-parameter="base_color" title="Выбор цвета каркаса">
<option value="chocolate">Шоколад</option>
<option value="graphite">Графит</option>
<option value="green_moss">Зелёный мох</option>
</select>
</div>
</div>
<div>
<label>Каркас</label>
<div>
<select name="base" data-calculator-parameter="base" title="Выбор каркаса" onchange="calculator.generate.fence(this.parentElement.parentElement.parentElement, this.value)">
<option value="1">Профильная труба</option>
<option value="2">П-образный профиль</option>
</select>
</div>
</div>
{% include 'calculators/elements/fences/1.html' %}
<div>
<label>Цвет профнастила</label>
<div>
<select name="fence_color" data-calculator-parameter="fence_color" title="Выбор цвета профнастила">
<option value="chocolate">Шоколад</option>
<option value="graphite">Графит</option>
<option value="green_moss">Зелёный мох</option>
</select>
</div>
</div>
<div>
<label>Тип монтажа</label>
<div>
<select name="mounting" data-calculator-parameter="mounting" title="Выбор типа монтажа">
<option value="ground">В грунт</option>
<option value="piles">На сваи/бетон</option>
</select>
</div>
</div>
<div>
<label>Высота</label>
<div>
<select name="height" data-calculator-parameter="height" title="Высота">
<option value="1800">1800мм</option>
<option value="2000">2000мм</option>
</select>
</div>
</div>
<div>
<label>Длина</label>
<div>
<input data-calculator-parameter="length" type="number" class="measured" title="Длина детали"
value="{{ calculators.profnastil.length.default ?? 1 }}" min="{{ calculators.profnastil.length.min ?? 1 }}"
max="{{ calculators.profnastil.length.max ?? 3000 }}">
<span class="unit unselectable">м</span>
</div>
</div>
<div>
<label>Калитки</label>
<div>
<input data-calculator-parameter="wickets" type="number" class="measured" title="Количество калиток"
value="{{ calculators.profnastil.wickets.default ?? 0 }}" min="{{ calculators.profnastil.wickets.min ?? 0 }}"
max="{{ calculators.profnastil.wickets.max ?? 100 }}">
<span class="unit unselectable">шт</span>
</div>
</div>
<div>
<label>Ворота</label>
<div>
<input data-calculator-parameter="gates" type="number" class="measured" title="Количество ворот"
value="{{ calculators.profnastil.gates.default ?? 0 }}" min="{{ calculators.profnastil.gates.min ?? 0 }}"
max="{{ calculators.profnastil.gates.max ?? 100 }}">
<span class="unit unselectable">шт</span>
</div>
</div>
</section>

View File

@ -0,0 +1,83 @@
<h3>Забор из профнастила<span title="Удалить"></span></h3>
<section class="calculator" data-calculator="laser">
<div>
<label>Тип</label>
<div>
<select name="type" data-calculator-parameter="type" title="Выбор типа сетки">
<option value="light">light</option>
<option value="medium">medium</option>
<option value="optima">optima</option>
</select>
</div>
</div>
<div>
<label>Цвет сетки</label>
<div>
<select name="fence_color" data-calculator-parameter="fence_color" title="Выбор цвета сетки">
<option value="graphite">Графит</option>
<option value="green_moss">Зелёный мох</option>
</select>
</div>
</div>
<div>
<label>Тип крепления</label>
<div>
<select name="fastening" data-calculator-parameter="fastening" title="Выбор типа крепления">
<option value="brace">Скоба</option>
<option value="collar">Хомут</option>
</select>
</div>
</div>
<div>
<label>Тип монтажа</label>
<div>
<select name="mounting" data-calculator-parameter="mounting" title="Выбор типа монтажа">
<option value="ground">В грунт</option>
<option value="piles">На сваи/бетон</option>
</select>
</div>
</div>
<div>
<label>Высота</label>
<div>
<select name="height" data-calculator-parameter="height" title="Высота">
<option value="1800">1800мм</option>
<option value="2000">2000мм</option>
</select>
</div>
</div>
<div>
<label>Длина</label>
<div>
<input data-calculator-parameter="length" type="number" class="measured" title="Длина детали"
value="{{ calculators.profnastil.length.default ?? 1 }}" min="{{ calculators.profnastil.length.min ?? 1 }}"
max="{{ calculators.profnastil.length.max ?? 3000 }}">
<span class="unit unselectable">м</span>
</div>
</div>
<div>
<label>Калитки</label>
<div>
<input data-calculator-parameter="wickets" type="number" class="measured" title="Количество калиток"
value="{{ calculators.profnastil.wickets.default ?? 0 }}" min="{{ calculators.profnastil.wickets.min ?? 0 }}"
max="{{ calculators.profnastil.wickets.max ?? 100 }}">
<span class="unit unselectable">шт</span>
</div>
</div>
<div>
<label>Ворота</label>
<div>
<input data-calculator-parameter="gates" type="number" class="measured" title="Количество ворот"
value="{{ calculators.profnastil.gates.default ?? 0 }}" min="{{ calculators.profnastil.gates.min ?? 0 }}"
max="{{ calculators.profnastil.gates.max ?? 100 }}">
<span class="unit unselectable">шт</span>
</div>
</div>
</section>

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\tordv\calculator\views; namespace mirzaev\zkmr\calculator\views;
use mirzaev\minimal\controller; use mirzaev\minimal\controller;
@ -12,7 +12,7 @@ use Twig\Environment as view;
/** /**
* Менеджер представлений * Менеджер представлений
* *
* @package mirzaev\tordv\calculator\controllers * @package mirzaev\zkmr\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class manager extends controller final class manager extends controller