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
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
-- Migration for v1.5.0: Yandex Disk integration + SLA Tickets + MAX notifications
|
||||
|
||||
-- File sync queue
|
||||
CREATE TABLE IF NOT EXISTS file_sync_queue (
|
||||
id SERIAL PRIMARY KEY,
|
||||
local_path VARCHAR(500) NOT NULL,
|
||||
remote_path VARCHAR(500) DEFAULT '',
|
||||
entity_type VARCHAR(50) NOT NULL,
|
||||
entity_id INTEGER NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','uploading','synced','error')),
|
||||
error TEXT DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_file_sync_status ON file_sync_queue(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_file_sync_entity ON file_sync_queue(entity_type, entity_id);
|
||||
|
||||
-- Bot tickets
|
||||
CREATE TABLE IF NOT EXISTS bot_tickets (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticket_number VARCHAR(20) UNIQUE NOT NULL,
|
||||
user_id INTEGER REFERENCES bot_users(id) ON DELETE SET NULL,
|
||||
object_id INTEGER REFERENCES objects(id) ON DELETE SET NULL,
|
||||
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
priority VARCHAR(20) DEFAULT 'normal' CHECK (priority IN ('low','normal','high','urgent')),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open','in_progress','resolved','closed')),
|
||||
created_by VARCHAR(20) DEFAULT 'bot',
|
||||
assigned_to INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
sla_contract_id INTEGER REFERENCES sla_contracts(id) ON DELETE SET NULL,
|
||||
has_attachment BOOLEAN DEFAULT FALSE,
|
||||
attachment_type VARCHAR(50) DEFAULT '',
|
||||
attachment_path VARCHAR(500) DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
resolved_at TIMESTAMP NULL,
|
||||
closed_at TIMESTAMP NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_ticket_status ON bot_tickets(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_ticket_user ON bot_tickets(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_ticket_object ON bot_tickets(object_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_ticket_number ON bot_tickets(ticket_number);
|
||||
|
||||
-- Bot ticket messages
|
||||
CREATE TABLE IF NOT EXISTS bot_ticket_messages (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticket_id INTEGER REFERENCES bot_tickets(id) ON DELETE CASCADE,
|
||||
direction VARCHAR(3) NOT NULL CHECK (direction IN ('in','out')),
|
||||
text TEXT DEFAULT '',
|
||||
has_attachment BOOLEAN DEFAULT FALSE,
|
||||
attachment_path VARCHAR(500) DEFAULT '',
|
||||
sender VARCHAR(20) DEFAULT 'client',
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_ticket_msg_ticket ON bot_ticket_messages(ticket_id);
|
||||
|
||||
-- Bot ticket status log
|
||||
CREATE TABLE IF NOT EXISTS bot_ticket_statuses (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ticket_id INTEGER REFERENCES bot_tickets(id) ON DELETE CASCADE,
|
||||
old_status VARCHAR(20) DEFAULT '',
|
||||
new_status VARCHAR(20) NOT NULL,
|
||||
changed_by VARCHAR(20) DEFAULT 'system',
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
comment TEXT DEFAULT ''
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_ticket_status_log ON bot_ticket_statuses(ticket_id);
|
||||
|
||||
-- Add max_user_id to users table
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS max_user_id BIGINT NULL;
|
||||
|
||||
-- Add Yandex Disk settings
|
||||
INSERT INTO bot_settings (key, value, description) VALUES
|
||||
('yandex_disk_token', 'y0__wgBEMHM9zsYiuxBIMi6sswXML_twIQIyCDWBrrNPjQcoQZR6J2x57oBLcg', 'OAuth токен Яндекс Диска'),
|
||||
('yandex_disk_root', '/AegisOne_Service', 'Корневая папка на Яндекс Диске'),
|
||||
('yandex_disk_client_id', '00fd594faf604161b773a53737bb5462', 'Client ID приложения Яндекс'),
|
||||
('yandex_disk_client_secret', '32a46b6dfd3c499e8cced25fed64b426', 'Client Secret приложения Яндекс'),
|
||||
('storage_backend', 'yandex', 'Режим хранения: local/yandex/both'),
|
||||
('max_file_size_mb', '100', 'Максимальный размер файла (MB)'),
|
||||
('public_link_expiry_days', '30', 'Срок публичных ссылок по умолчанию (дни)'),
|
||||
('auto_archive_enabled', 'true', 'Авто-архивация закрытых обращений'),
|
||||
('auto_archive_after_days', '30', 'Через сколько дней архивировать'),
|
||||
('max_group_chat_id', '', 'ID группового чата MAX для уведомлений')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- Add SLA Ticket feature
|
||||
INSERT INTO bot_features (feature_key, name, description, enabled, sort_order) VALUES
|
||||
('sla_ticket', 'Заявка по SLA', 'Подать заявку о неисправности для клиентов с активным SLA', true, 0)
|
||||
ON CONFLICT (feature_key) DO NOTHING;
|
||||
|
||||
-- Add ticket-related response templates
|
||||
INSERT INTO bot_response_templates (template_key, template_text, description, variables) VALUES
|
||||
('sla_ticket_check', 'Проверяю ваш SLA контракт...', 'Проверка SLA перед созданием заявки', '[]'),
|
||||
('sla_ticket_found', 'SLA найден: {object_name}, уровень: {service_level}. Опишите неисправность. Вы можете прикрепить фото или документ.', 'SLA найден, запрос описания', '["object_name","service_level"]'),
|
||||
('sla_ticket_not_found', 'SLA контракт не найден или не активен. Свяжитесь с нами:\n\n📞 {phone_1}\n📞 {phone_2}\n✉️ {support_email}', 'SLA не найден', '["phone_1","phone_2","support_email"]'),
|
||||
('sla_ticket_created', 'Заявка #{ticket_number} создана.\n\nПриоритет: {priority}\nОжидаемое время реакции: {response_time}\n\nИнженер будет назначен в ближайшее время.', 'Подтверждение создания заявки', '["ticket_number","priority","response_time"]'),
|
||||
('ticket_status_update', 'Ваша заявка #{ticket_number} — {status}.\nИнженер: {engineer}', 'Уведомление о смене статуса заявки', '["ticket_number","status","engineer"]'),
|
||||
('ticket_resolved', 'Заявка #{ticket_number} выполнена. Оцените качество:\n\n⭐ 1 — Плохо\n⭐⭐ 2\n⭐⭐⭐ 3\n⭐⭐⭐⭐ 4\n⭐⭐⭐⭐⭐ 5 — Отлично', 'Запрос оценки после выполнения', '["ticket_number"]')
|
||||
ON CONFLICT (template_key) DO NOTHING;
|
||||
Reference in New Issue
Block a user