72b6879f4b
- Refactored max_bot from nested packages to flat module structure - Q2: Extended BotUser model (patronymic, email, org, address, vcf_raw, contact_hash, phone_verified, email_verified, last_interaction, total_conversations, total_tickets) - Q2: VCF parser (FN, N, TEL, EMAIL, ORG, ADR), upsert on re-contact, NLP history context (_get_user_history_context -> YandexGPT) - Q1: Broadcast preview modal with 10s confirmation timer - Q3: CSS var(--white)->var(--bg-card), var(--text)->var(--text-primary) - Q4: bot_settings showNotification(), editable max_bot_id - Q5: Webhook secret passthrough via X-Max-Bot-Api-Secret - Masking sensitive keys, dialog_cleared handler, migrate via _add_column_if_not_exists() - Rate limit (asyncio.sleep 0.5 per 10), dead code removed, conv.intent context in contact.py - Portal pages: bot_consent, bot_kb (edit), bot_settings, bot_test, bot_tickets, portal_settings - Tests: 21/21 passing, added test_yandex_gpt.py, test_email_sender.py - Deploy: deploy_full.sh, schema.sql, seed_knowledge_base.sql
243 lines
12 KiB
PHP
243 lines
12 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../../inc/auth.php';
|
|
require_once __DIR__ . '/../../inc/functions.php';
|
|
$session = auth_require('owner');
|
|
|
|
$pdo = getDB();
|
|
$message = '';
|
|
$msgType = 'info';
|
|
|
|
// Update coefficient
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
$csrf = $_POST['csrf_token'] ?? '';
|
|
|
|
if (!auth_csrf_verify($csrf)) {
|
|
$message = 'Ошибка безопасности.';
|
|
$msgType = 'error';
|
|
} elseif ($_POST['action'] === 'edit' || $_POST['action'] === 'add') {
|
|
$key = trim($_POST['key'] ?? '');
|
|
$value = str_replace(',', '.', trim($_POST['value'] ?? ''));
|
|
$description = trim($_POST['description'] ?? '');
|
|
$formulaRef = trim($_POST['formula_ref'] ?? '');
|
|
$id = (int)($_POST['id'] ?? 0);
|
|
|
|
if ($key === '' || $value === '' || !is_numeric($value)) {
|
|
$message = 'Заполните ключ и числовое значение.';
|
|
$msgType = 'error';
|
|
} else {
|
|
try {
|
|
$oldVal = null;
|
|
if ($id > 0) {
|
|
$stmt = $pdo->prepare('SELECT "key", "value" FROM formula_coefficients WHERE id=?');
|
|
$stmt->execute([$id]);
|
|
$old = $stmt->fetch();
|
|
$oldVal = $old ? $old['value'] : null;
|
|
|
|
$stmt = $pdo->prepare('UPDATE formula_coefficients SET "key"=?, "value"=?, "description"=?, "formula_ref"=?, "updated_by"=? WHERE id=?');
|
|
$stmt->execute([$key, $value, $description, $formulaRef, $session['user_id'], $id]);
|
|
} else {
|
|
$stmt = $pdo->prepare('INSERT INTO formula_coefficients ("key", "value", "description", "formula_ref", "updated_by") VALUES (?,?,?,?,?)');
|
|
$stmt->execute([$key, $value, $description, $formulaRef, $session['user_id']]);
|
|
}
|
|
$message = 'Коэффициент "' . htmlspecialchars($key) . '" сохранён.';
|
|
$msgType = 'success';
|
|
$detail = "Key: $key, new: $value" . ($oldVal !== null ? ", old: $oldVal" : '');
|
|
auth_log($session['user_id'], 'coefficient_update', $detail);
|
|
} catch (\PDOException $e) {
|
|
$message = 'Ошибка: ' . ($e->getCode() === '23000' ? 'Ключ уже существует.' : $e->getMessage());
|
|
$msgType = 'error';
|
|
}
|
|
}
|
|
} elseif ($_POST['action'] === 'toggle') {
|
|
$id = (int)($_POST['id'] ?? 0);
|
|
$stmt = $pdo->prepare('SELECT "key", is_active FROM formula_coefficients WHERE id=?');
|
|
$stmt->execute([$id]);
|
|
$c = $stmt->fetch();
|
|
if ($c) {
|
|
$newState = $c['is_active'] ? 0 : 1;
|
|
$stmt = $pdo->prepare("UPDATE formula_coefficients SET is_active=?, updated_by=? WHERE id=?");
|
|
$stmt->execute([$newState, $session['user_id'], $id]);
|
|
auth_log($session['user_id'], 'coefficient_toggle', "Key: {$c['key']}, active: $newState");
|
|
$message = 'Статус коэффициента изменён.';
|
|
$msgType = 'info';
|
|
}
|
|
} elseif ($_POST['action'] === 'reset_defaults') {
|
|
$confirmed = $_POST['confirmed'] ?? '';
|
|
if ($confirmed === '1') {
|
|
auth_log($session['user_id'], 'coefficient_reset', 'Сброс всех коэффициентов к значениям из migration.sql');
|
|
$message = 'Сброс не реализован — удалите таблицу и перезапустите migration.sql вручную.';
|
|
$msgType = 'warning';
|
|
} else {
|
|
$message = 'Подтвердите сброс.';
|
|
$msgType = 'warning';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get edit data
|
|
$editCoeff = null;
|
|
if (isset($_GET['edit'])) {
|
|
$stmt = $pdo->prepare("SELECT * FROM formula_coefficients WHERE id=?");
|
|
$stmt->execute([(int)$_GET['edit']]);
|
|
$editCoeff = $stmt->fetch();
|
|
}
|
|
|
|
// Get all coefficients ordered by key
|
|
$coeffs = $pdo->query('SELECT * FROM formula_coefficients ORDER BY "key"')->fetchAll();
|
|
|
|
// Group by prefix
|
|
$groups = [];
|
|
foreach ($coeffs as $c) {
|
|
$prefix = explode('.', $c['key'])[0];
|
|
$groups[$prefix][] = $c;
|
|
}
|
|
|
|
$csrf = auth_csrf_token();
|
|
require __DIR__ . '/../../inc/header.php';
|
|
?>
|
|
|
|
<div class="main-header">
|
|
<div>
|
|
<h1>Коэффициенты формул</h1>
|
|
<div class="breadcrumb"><a href="/service/dashboard.php">Главная</a> / Коэффициенты</div>
|
|
</div>
|
|
<div style="display:flex;gap:8px;">
|
|
<button class="btn btn-primary btn-sm" onclick="document.getElementById('addModal').classList.add('open')" data-tooltip="Добавить коэффициент">+ Добавить</button>
|
|
<form method="post" style="display:inline;" onsubmit="return confirm('Сбросить все коэффициенты к значениям по умолчанию? Это удалит изменения.');">
|
|
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
|
<input type="hidden" name="action" value="reset_defaults">
|
|
<input type="hidden" name="confirmed" value="1">
|
|
<button type="submit" class="btn btn-danger btn-sm">Сбросить</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($message): ?>
|
|
<div class="alert alert-<?= $msgType ?>"><?= htmlspecialchars($message) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php foreach ($groups as $prefix => $items): ?>
|
|
<div class="card" style="margin-bottom:20px;">
|
|
<div class="card-header">
|
|
<h2 style="text-transform:uppercase;font-size:13px;letter-spacing:0.5px;color:var(--accent);"><?= htmlspecialchars($prefix) ?></h2>
|
|
<span style="font-size:11px;color:var(--text-muted);"><?= count($items) ?> параметров</span>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="min-width:220px;">Ключ</th>
|
|
<th style="min-width:120px;">Значение</th>
|
|
<th>Описание</th>
|
|
<th>Функция</th>
|
|
<th style="width:100px;">Статус</th>
|
|
<th class="actions" style="width:140px;">Действия</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($items as $c): ?>
|
|
<tr>
|
|
<td><code style="font-size:12px;color:var(--accent);"><?= htmlspecialchars($c['key']) ?></code></td>
|
|
<td><strong><?= rtrim(rtrim($c['value'], '0'), '.') ?></strong></td>
|
|
<td style="font-size:12px;color:var(--text-secondary);"><?= htmlspecialchars($c['description'] ?: '—') ?></td>
|
|
<td style="font-size:11px;color:var(--text-muted);"><?= htmlspecialchars($c['formula_ref'] ?: '—') ?></td>
|
|
<td>
|
|
<form method="post" style="display:inline;">
|
|
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
|
<input type="hidden" name="action" value="toggle">
|
|
<input type="hidden" name="id" value="<?= $c['id'] ?>">
|
|
<button type="submit" class="btn btn-sm <?= $c['is_active'] ? 'btn-success' : 'btn-secondary' ?>"><?= $c['is_active'] ? 'Активен' : 'Отключён' ?></button>
|
|
</form>
|
|
</td>
|
|
<td class="actions">
|
|
<a href="?edit=<?= $c['id'] ?>" class="btn btn-secondary btn-sm" onclick="event.preventDefault();openEdit(<?= $c['id'] ?>, '<?= htmlspecialchars($c['key'], ENT_QUOTES) ?>', '<?= rtrim(rtrim($c['value'], '0'), '.') ?>', '<?= htmlspecialchars($c['description'] ?? '', ENT_QUOTES) ?>', '<?= htmlspecialchars($c['formula_ref'] ?? '', ENT_QUOTES) ?>')">✏️</a>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
|
|
<!-- Edit Modal -->
|
|
<div class="modal-overlay <?= $editCoeff ? 'open' : '' ?>" id="editModal">
|
|
<div class="modal">
|
|
<button class="modal-close" onclick="closeEdit()">×</button>
|
|
<h2 id="editModalTitle">Редактировать коэффициент</h2>
|
|
<form method="post">
|
|
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
|
<input type="hidden" name="action" value="edit">
|
|
<input type="hidden" name="id" id="editId" value="<?= $editCoeff['id'] ?? 0 ?>">
|
|
|
|
<div class="form-group">
|
|
<label for="editKey">Ключ</label>
|
|
<input type="text" id="editKey" name="key" value="<?= htmlspecialchars($editCoeff['key'] ?? '') ?>" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editValue">Значение *</label>
|
|
<input type="text" id="editValue" name="value" value="<?= htmlspecialchars($editCoeff['value'] ?? '') ?>" required placeholder="0.0000">
|
|
<div style="font-size:11px;color:var(--text-muted);margin-top:4px;">Число с точкой или запятой</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editDesc">Описание</label>
|
|
<input type="text" id="editDesc" name="description" value="<?= htmlspecialchars($editCoeff['description'] ?? '') ?>">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="editRef">Функция / ссылка на формулу</label>
|
|
<input type="text" id="editRef" name="formula_ref" value="<?= htmlspecialchars($editCoeff['formula_ref'] ?? '') ?>">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Сохранить</button>
|
|
<button type="button" class="btn btn-secondary" onclick="closeEdit()">Отмена</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Modal -->
|
|
<div class="modal-overlay" id="addModal">
|
|
<div class="modal">
|
|
<button class="modal-close" onclick="document.getElementById('addModal').classList.remove('open')">×</button>
|
|
<h2>Добавить коэффициент</h2>
|
|
<form method="post">
|
|
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
|
<input type="hidden" name="action" value="add">
|
|
|
|
<div class="form-group">
|
|
<label for="addKey">Ключ *</label>
|
|
<input type="text" id="addKey" name="key" required placeholder="напр. sla_price.base_cost">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="addValue">Значение *</label>
|
|
<input type="text" id="addValue" name="value" required placeholder="0.0000">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="addDesc">Описание</label>
|
|
<input type="text" id="addDesc" name="description">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="addRef">Функция / ссылка</label>
|
|
<input type="text" id="addRef" name="formula_ref">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Создать</button>
|
|
<button type="button" class="btn btn-secondary" onclick="document.getElementById('addModal').classList.remove('open')">Отмена</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openEdit(id, key, value, desc, ref) {
|
|
document.getElementById('editId').value = id;
|
|
document.getElementById('editKey').value = key;
|
|
document.getElementById('editValue').value = value;
|
|
document.getElementById('editDesc').value = desc;
|
|
document.getElementById('editRef').value = ref;
|
|
document.getElementById('editModal').classList.add('open');
|
|
}
|
|
function closeEdit() {
|
|
document.getElementById('editModal').classList.remove('open');
|
|
window.location.href = 'coefficients.php';
|
|
}
|
|
</script>
|
|
|
|
<?php require __DIR__ . '/../../inc/footer.php'; ?>
|