Files
angel 72b6879f4b v1.7.0: refactor max_bot to flat structure, add VCF+UserModel+NLP history context, portal pages and proxy fixes
- 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
2026-05-29 02:30:30 +03:00

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()">&times;</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')">&times;</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'; ?>