-- Bot tables for AegisOne Engineering Max Bot "София" -- Run against the shared aegisone database CREATE TABLE IF NOT EXISTS bot_users ( id SERIAL PRIMARY KEY, max_user_id BIGINT UNIQUE NOT NULL, first_name VARCHAR(255) DEFAULT '', last_name VARCHAR(255) DEFAULT '', username VARCHAR(255) DEFAULT '', phone VARCHAR(50) DEFAULT '', email VARCHAR(255) DEFAULT '', consent_given BOOLEAN NOT NULL DEFAULT FALSE, consent_timestamp TIMESTAMP NULL, consent_method VARCHAR(50) DEFAULT '', created_at TIMESTAMP NOT NULL DEFAULT NOW(), last_active_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS bot_categories ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, description VARCHAR(500) DEFAULT '', active BOOLEAN NOT NULL DEFAULT TRUE, sort_order INTEGER DEFAULT 0, icon_emoji VARCHAR(10) DEFAULT '📋', keywords JSONB DEFAULT '[]'::jsonb ); CREATE TABLE IF NOT EXISTS bot_conversations ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES bot_users(id) ON DELETE CASCADE, status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open','closed','handoff','1c_sent','awaiting_rating')), inquiry_type INTEGER REFERENCES bot_categories(id) ON DELETE SET NULL, inquiry_text TEXT DEFAULT '', priority VARCHAR(20) DEFAULT 'normal' CHECK (priority IN ('low','normal','high','urgent')), created_at TIMESTAMP NOT NULL DEFAULT NOW(), closed_at TIMESTAMP NULL, assigned_to VARCHAR(255) DEFAULT '', has_attachment BOOLEAN DEFAULT FALSE, attachment_type VARCHAR(50) DEFAULT '', attachment_path VARCHAR(500) DEFAULT '' ); CREATE TABLE IF NOT EXISTS bot_messages ( id SERIAL PRIMARY KEY, conversation_id INTEGER REFERENCES bot_conversations(id) ON DELETE CASCADE, direction VARCHAR(3) NOT NULL CHECK (direction IN ('in','out')), text TEXT DEFAULT '', has_attachment BOOLEAN DEFAULT FALSE, attachment_type VARCHAR(50) DEFAULT '', attachment_path VARCHAR(500) DEFAULT '', max_message_id BIGINT NULL, timestamp TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS bot_consent_logs ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES bot_users(id) ON DELETE CASCADE, action VARCHAR(20) NOT NULL CHECK (action IN ('given','revoked')), timestamp TIMESTAMP NOT NULL DEFAULT NOW(), method VARCHAR(50) DEFAULT '', ip_address VARCHAR(45) DEFAULT '' ); CREATE TABLE IF NOT EXISTS bot_1c_sync_log ( id SERIAL PRIMARY KEY, conversation_id INTEGER REFERENCES bot_conversations(id) ON DELETE CASCADE, status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sent','confirmed','error')), request_payload TEXT DEFAULT '', response_payload TEXT DEFAULT '', sent_at TIMESTAMP NULL, confirmed_at TIMESTAMP NULL ); CREATE TABLE IF NOT EXISTS bot_knowledge_base ( id SERIAL PRIMARY KEY, question VARCHAR(500) NOT NULL, answer TEXT NOT NULL, category VARCHAR(100) DEFAULT '', keywords JSONB DEFAULT '[]'::jsonb, active BOOLEAN NOT NULL DEFAULT TRUE, sort_order INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS bot_settings ( id SERIAL PRIMARY KEY, key VARCHAR(100) UNIQUE NOT NULL, value TEXT DEFAULT '', description VARCHAR(500) DEFAULT '' ); CREATE TABLE IF NOT EXISTS bot_features ( id SERIAL PRIMARY KEY, feature_key VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(100) NOT NULL, description VARCHAR(500) DEFAULT '', enabled BOOLEAN NOT NULL DEFAULT FALSE, sort_order INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS bot_response_templates ( id SERIAL PRIMARY KEY, template_key VARCHAR(100) UNIQUE NOT NULL, template_text TEXT NOT NULL, description VARCHAR(500) DEFAULT '', variables JSONB DEFAULT '[]'::jsonb ); CREATE TABLE IF NOT EXISTS bot_broadcasts ( id SERIAL PRIMARY KEY, text TEXT NOT NULL, sent_at TIMESTAMP NULL, sent_by INTEGER NULL, recipient_count INTEGER DEFAULT 0, status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('draft','sent','failed')) ); -- Indexes CREATE INDEX IF NOT EXISTS idx_bot_conv_user ON bot_conversations(user_id); CREATE INDEX IF NOT EXISTS idx_bot_conv_status ON bot_conversations(status); CREATE INDEX IF NOT EXISTS idx_bot_msg_conv ON bot_messages(conversation_id); CREATE INDEX IF NOT EXISTS idx_bot_consent_user ON bot_consent_logs(user_id); CREATE INDEX IF NOT EXISTS idx_bot_1c_conv ON bot_1c_sync_log(conversation_id); CREATE INDEX IF NOT EXISTS idx_bot_1c_status ON bot_1c_sync_log(status); CREATE INDEX IF NOT EXISTS idx_bot_kb_active ON bot_knowledge_base(active); CREATE INDEX IF NOT EXISTS idx_bot_cat_active ON bot_categories(active); -- Initial settings INSERT INTO bot_settings (key, value, description) VALUES ('bot_token', 'id2311381465_bot', 'Токен бота Max'), ('assistant_name', 'София', 'Имя виртуального помощника'), ('phone_1', '8 (861) 203-33-30', 'Телефон 1'), ('phone_2', '8 (995) 203-33-30', 'Телефон 2'), ('support_email', 'zakaz@aegisone.ru', 'Email поддержки'), ('email_subject', 'Обращение через Виртуального помощника AegisOne Engineering', 'Тема письма при отказе от согласия'), ('work_hours_start', '9', 'Начало рабочего времени (час)'), ('work_hours_end', '18', 'Конец рабочего времени (час)'), ('work_days', '1,2,3,4,5', 'Рабочие дни (1=Пн, 7=Вс)'), ('timezone', 'Europe/Moscow', 'Часовой пояс'), ('1c_webhook_url', '', 'URL webhook 1С:УНФ'), ('consent_policy_url', 'https://aegisone.ru/politica.php', 'Ссылка на политику ПД'), ('yandex_gpt_key', '', 'Ключ YandexGPT (пусто = ключевые слова)'), ('yandex_folder_id', '', 'Folder ID YandexGPT') ON CONFLICT (key) DO NOTHING; -- Initial features INSERT INTO bot_features (feature_key, name, description, enabled, sort_order) VALUES ('risk_score', 'Risk Score калькулятор', 'Расчёт оценки рисков объекта', false, 1), ('sla_status', 'Проверка SLA статуса', 'Проверка статуса SLA контракта по номеру объекта', false, 2), ('photo_report', 'Фото-отчёт', 'Отправка фото проблемы с созданием инцидента', false, 3), ('appointment', 'Запись на визит', 'Запись на визит инженера', false, 4), ('quality_rate', 'Оценка качества', 'Оценка качества после закрытия обращения', false, 5), ('commercial', 'Коммерческое предложение', 'Генерация КП по запросу', false, 6), ('reminders', 'Напоминания о ТО', 'Автоматические напоминания о плановом ТО', false, 7), ('my_objects', 'Мои объекты', 'Просмотр информации по объектам клиента', false, 8), ('notifications', 'Уведомления о статусе', 'Автоматические уведомления при изменении статуса', false, 9), ('broadcasts', 'Рассылки', 'Массовая рассылка сообщений клиентам', false, 10), ('sla_ticket', 'SLA заявки', 'Подача заявки о неисправности клиентом с активным SLA', false, 11) ON CONFLICT (feature_key) DO NOTHING; -- Initial categories INSERT INTO bot_categories (name, description, icon_emoji, keywords, sort_order) VALUES ('Видеонаблюдение', 'Камеры, видеорегистраторы, хранилища', '📹', '["камер","видео","наблюден","регистратор","хранен","запись"]', 1), ('СКУД', 'Контроль доступа, турникеты, замки', '🔐', '["скуд","доступ","турникет","замок","пропуск","карт"]', 2), ('Пожарная сигнализация', 'Датчики, оповещение, пожаротушение', '🔥', '["пожар","сигнализаци","датчик","оповещен","пожаротушен","дым"]', 3), ('IT-инфраструктура', 'Серверы, сеть, компьютеры', '💻', '["сервер","сет","компьютер","ит","инфраструктур","switch","роутер"]', 4), ('Обслуживание', 'Плановое ТО, ремонт, замена', '🔧', '["обслуживан","ремонт","замен","то","планов","срок"]', 5), ('Консультация', 'Вопросы, информация, расчёт стоимости', '💬', '["консультаци","вопрос","информац","стоимост","расчёт","цен"]', 6), ('Другое', 'Прочие обращения', '📋', '[]', 7) ON CONFLICT DO NOTHING; -- Initial response templates INSERT INTO bot_response_templates (template_key, template_text, description, variables) VALUES ('greeting', 'Здравствуйте! Я {name}, виртуальный помощник AegisOne Engineering. Чем могу помочь?', 'Приветствие нового пользователя', '["name"]'), ('greeting_returning', 'Здравствуйте, {first_name}! У вас открыто обращение #{conv_id}. Чем ещё могу помочь?', 'Приветствие возвращающегося пользователя', '["first_name","conv_id"]'), ('consent_request', 'Для обработки обращения нам нужны контактные данные. Даёте согласие на обработку персональных данных?\n\n[Подробнее о политике]({policy_url})', 'Запрос согласия ФЗ-152', '["policy_url"]'), ('consent_refused', 'Понимаем. Вы можете связаться с нами:\n\n📞 {phone_1}\n📞 {phone_2}\n✉️ {support_email}', 'Ответ при отказе от согласия', '["phone_1","phone_2","support_email"]'), ('name_request', 'Как к вам обращаться?', 'Запрос имени', '[]'), ('contact_request', 'Как с вами связаться?', 'Запрос контактных данных', '[]'), ('inquiry_request', 'Опишите ваш вопрос. Вы можете прикрепить фото или документ (PDF, DOCX, XLSX, TXT).', 'Запрос обращения', '[]'), ('out_of_hours', 'Спасибо за обращение! Сейчас мы не работаем.\n\nНаше рабочее время: Пн-Пт {work_hours_start}:00-{work_hours_end}:00 (МСК).\n\nНо вы можете оставить сообщение — мы ответим как можно быстрее.', 'Сообщение вне рабочего времени', '["work_hours_start","work_hours_end"]'), ('confirmation', 'Спасибо, {first_name}! Обращение #{conv_id} принято.\n\nОтветим как можно быстрее.', 'Подтверждение (рабочее время)', '["first_name","conv_id"]'), ('confirmation_ooh', 'Спасибо, {first_name}! Обращение #{conv_id} принято.\n\nСейчас нерабочее время, но мы ответим как можно быстрее.', 'Подтверждение (вне рабочего времени)', '["first_name","conv_id"]'), ('handoff', 'Передаю обращение оператору. Ожидайте.', 'Передача оператору', '[]'), ('quality_request', 'Обращение #{conv_id} выполнено. Оцените качество нашей работы:\n\n⭐ 1 — Плохо\n⭐⭐ 2\n⭐⭐⭐ 3\n⭐⭐⭐⭐ 4\n⭐⭐⭐⭐⭐ 5 — Отлично', 'Запрос оценки качества', '["conv_id"]'), ('sla_found', 'Объект: {object_name}\nSLA: {service_level}\nСтатус: {status}\nЦена/мес: {price} ₽', 'Результат проверки SLA', '["object_name","service_level","status","price"]'), ('sla_not_found', 'Объект не найден или SLA контракт не оформлен.', 'SLA не найден', '[]'), ('risk_result', 'Risk Score вашего объекта: {score}/100\nУровень: {label}\n\nРекомендуем: {recommendation}', 'Результат Risk Score', '["score","label","recommendation"]'), ('appointment_confirm', 'Запись на визит подтверждена.\n\nДата: {date}\nИнженер: {engineer}', 'Подтверждение записи на визит', '["date","engineer"]'), ('commercial_offer', 'Коммерческое предложение по вашему запросу:\n\n{offer_text}', 'Коммерческое предложение', '["offer_text"]'), ('reminder', 'Напоминаем: плановое ТО объекта {object_name} запланировано на {date}.', 'Напоминание о ТО', '["object_name","date"]'), ('status_update', 'Ваше обращение #{conv_id} — {status}.\nИнженер: {engineer}.', 'Уведомление о статусе', '["conv_id","status","engineer"]'), ('main_menu', 'Выберите действие:', 'Текст главного меню', '[]'), ('emotion_escalate', 'Я понимаю ваше беспокойство. Передаю обращение старшему специалисту. Ожидайте, пожалуйста.', 'Реакция на негативные эмоции', '[]'), ('auto_categorize_confirm', 'Я определил вашу тему как: {category}. Это верно?', 'Подтверждение авто-категоризации', '["category"]') ON CONFLICT (template_key) DO NOTHING; -- Sample knowledge base entries INSERT INTO bot_knowledge_base (question, answer, category, keywords, active, sort_order) VALUES ('Какие услуги вы оказываете?', 'Мы оказываем услуги по проектированию, монтажу и обслуживанию систем видеонаблюдения, СКУД, пожарной сигнализации и IT-инфраструктуры.', 'Общее', '["услуг","оказыва","монтаж","проект","обслуживан"]', true, 1), ('Как оставить заявку?', 'Нажмите кнопку «Оставить заявку» в главном меню и опишите ваш вопрос.', 'Общее', '["заявк","оставит","как"]', true, 2), ('Какие гарантии?', 'Мы предоставляем гарантию на все выполненные работы и установленное оборудование. Подробности уточняйте у менеджера.', 'Общее', '["гарант","условия"]', true, 3), ('Режим работы?', 'Наш офис работает Пн-Пт с 9:00 до 18:00 (МСК).', 'Общее', '["режим","работ","время","график"]', true, 4), ('Как связаться?', 'Вы можете написать нам здесь, позвонить по телефону 8 (861) 203-33-30 или написать на zakaz@aegisone.ru', 'Общее', '["связат","контакт","телефон","почт"]', true, 5) ON CONFLICT DO NOTHING;