This commit is contained in:
2026-01-27 22:35:09 +05:00
parent a1c9c833f5
commit eb03509c26
2 changed files with 850 additions and 139 deletions

View File

@@ -93,8 +93,8 @@ function initializeSQLite() {
}
function createSQLiteTables() {
// notification_history
db.run(`CREATE TABLE IF NOT EXISTS notification_history (
// Таблица для истории уведомлений
db.run(`CREATE TABLE IF NOT EXISTS notification_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
task_id INTEGER NOT NULL,
@@ -105,7 +105,34 @@ function createSQLiteTables() {
FOREIGN KEY (task_id) REFERENCES tasks (id),
UNIQUE(user_id, task_id, notification_type)
)`);
// SQLite таблицы
// Таблица очереди email
db.run(`CREATE TABLE IF NOT EXISTS email_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
to_email TEXT NOT NULL,
subject TEXT NOT NULL,
html_content TEXT NOT NULL,
user_id INTEGER,
task_id INTEGER,
notification_type TEXT,
retry_count INTEGER DEFAULT 0,
status TEXT DEFAULT 'pending',
error_message TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (task_id) REFERENCES tasks(id)
)`);
// Таблица настроек email (для хранения состояния блокировки)
db.run(`CREATE TABLE IF NOT EXISTS email_settings (
setting_key TEXT PRIMARY KEY,
setting_value TEXT,
spam_blocked_until DATETIME,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// Основные таблицы системы
db.run(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
login TEXT UNIQUE NOT NULL,
@@ -137,6 +164,9 @@ function createSQLiteTables() {
rework_comment TEXT,
closed_at DATETIME,
closed_by INTEGER,
task_type TEXT DEFAULT 'regular',
approver_group_id INTEGER,
document_id INTEGER,
FOREIGN KEY (created_by) REFERENCES users (id),
FOREIGN KEY (deleted_by) REFERENCES users (id),
FOREIGN KEY (original_task_id) REFERENCES tasks (id),
@@ -189,7 +219,7 @@ function createSQLiteTables() {
console.log('✅ База данных SQLite инициализирована');
// Добавляем таблицу для пользовательских настроек
// Таблица для пользовательских настроек
db.run(`CREATE TABLE IF NOT EXISTS user_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER UNIQUE NOT NULL,
@@ -310,9 +340,7 @@ function createSQLiteTables() {
console.log('✅ Таблицы для согласования документов созданы');
// ===== НОВЫЕ ТАБЛИЦЫ ДЛЯ ГРУПП ПОЛЬЗОВАТЕЛЕЙ =====
// Таблица для групп пользователей
// Таблицы для групп пользователей
db.run(`CREATE TABLE IF NOT EXISTS user_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
@@ -323,7 +351,6 @@ function createSQLiteTables() {
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// Таблица для связи пользователей с группами (многие-ко-многим)
db.run(`CREATE TABLE IF NOT EXISTS user_group_memberships (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
@@ -361,12 +388,77 @@ function createSQLiteTables() {
console.log('✅ Таблицы для типов документов и документов (расширенные задачи) созданы');
// Создаем индексы для улучшения производительности
createSQLiteIndexes();
// Запускаем проверку и обновление структуры таблиц
setTimeout(() => {
checkAndUpdateTableStructure();
}, 2000);
}
function createSQLiteIndexes() {
console.log('🔧 Создаем индексы для SQLite...');
const indexes = [
// Индексы для очереди email
"CREATE INDEX IF NOT EXISTS idx_email_queue_status ON email_queue(status, created_at)",
"CREATE INDEX IF NOT EXISTS idx_email_queue_user_id ON email_queue(user_id)",
"CREATE INDEX IF NOT EXISTS idx_email_queue_task_id ON email_queue(task_id)",
// Индексы для уведомлений
"CREATE INDEX IF NOT EXISTS idx_notification_history_user_id ON notification_history(user_id)",
"CREATE INDEX IF NOT EXISTS idx_notification_history_task_id ON notification_history(task_id)",
"CREATE INDEX IF NOT EXISTS idx_notification_history_type ON notification_history(notification_type)",
"CREATE INDEX IF NOT EXISTS idx_notification_history_sent_at ON notification_history(last_sent_at)",
// Индексы для пользователей
"CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)",
"CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)",
// Индексы для задач
"CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by)",
"CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)",
"CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type)",
"CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date)",
"CREATE INDEX IF NOT EXISTS idx_tasks_closed_at ON tasks(closed_at)",
// Индексы для назначений задач
"CREATE INDEX IF NOT EXISTS idx_task_assignments_task_id ON task_assignments(task_id)",
"CREATE INDEX IF NOT EXISTS idx_task_assignments_user_id ON task_assignments(user_id)",
"CREATE INDEX IF NOT EXISTS idx_task_assignments_status ON task_assignments(status)",
// Индексы для файлов
"CREATE INDEX IF NOT EXISTS idx_task_files_task_id ON task_files(task_id)",
// Индексы для логов активности
"CREATE INDEX IF NOT EXISTS idx_activity_logs_task_id ON activity_logs(task_id)",
"CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs(created_at)",
// Индексы для настроек пользователей
"CREATE INDEX IF NOT EXISTS idx_user_settings_user_id ON user_settings(user_id)",
// Индексы для групп
"CREATE INDEX IF NOT EXISTS idx_user_groups_can_approve ON user_groups(can_approve_documents)",
"CREATE INDEX IF NOT EXISTS idx_user_group_memberships_user_id ON user_group_memberships(user_id)",
"CREATE INDEX IF NOT EXISTS idx_user_group_memberships_group_id ON user_group_memberships(group_id)",
// Индексы для простых документов
"CREATE INDEX IF NOT EXISTS idx_simple_documents_task_id ON simple_documents(task_id)",
"CREATE INDEX IF NOT EXISTS idx_simple_documents_document_number ON simple_documents(document_number)"
];
indexes.forEach(indexQuery => {
db.run(indexQuery, (err) => {
if (err) {
console.error(`❌ Ошибка создания индекса: ${err.message}`);
} else {
console.log(`✅ Индекс создан: ${indexQuery.split('ON')[1]}`);
}
});
});
}
// Функция для проверки и обновления структуры таблиц
function checkAndUpdateTableStructure() {
console.log('🔍 Проверка структуры таблиц...');
@@ -403,7 +495,6 @@ function checkAndUpdateTableStructure() {
{ name: 'rework_comment', type: 'TEXT' },
{ name: 'closed_at', type: 'DATETIME' },
{ name: 'closed_by', type: 'INTEGER' },
// Новые колонки для типа задач
{ name: 'task_type', type: 'TEXT DEFAULT "regular"' },
{ name: 'approver_group_id', type: 'INTEGER' },
{ name: 'document_id', type: 'INTEGER' }
@@ -454,6 +545,34 @@ function checkAndUpdateTableStructure() {
{ name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' },
{ name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }
],
notification_history: [
{ name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' },
{ name: 'user_id', type: 'INTEGER NOT NULL' },
{ name: 'task_id', type: 'INTEGER NOT NULL' },
{ name: 'notification_type', type: 'TEXT NOT NULL' },
{ name: 'last_sent_at', type: 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP' },
{ name: 'created_at', type: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP' }
],
email_queue: [
{ name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' },
{ name: 'to_email', type: 'TEXT NOT NULL' },
{ name: 'subject', type: 'TEXT NOT NULL' },
{ name: 'html_content', type: 'TEXT NOT NULL' },
{ name: 'user_id', type: 'INTEGER' },
{ name: 'task_id', type: 'INTEGER' },
{ name: 'notification_type', type: 'TEXT' },
{ name: 'retry_count', type: 'INTEGER DEFAULT 0' },
{ name: 'status', type: 'TEXT DEFAULT "pending"' },
{ name: 'error_message', type: 'TEXT' },
{ name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' },
{ name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }
],
email_settings: [
{ name: 'setting_key', type: 'TEXT PRIMARY KEY' },
{ name: 'setting_value', type: 'TEXT' },
{ name: 'spam_blocked_until', type: 'DATETIME' },
{ name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }
],
user_groups: [
{ name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' },
{ name: 'name', type: 'TEXT NOT NULL UNIQUE' },
@@ -527,67 +646,24 @@ function checkAndUpdateTableStructure() {
});
});
// Проверяем индекс для таблицы user_settings
// Создаем группу "Секретарь" по умолчанию, если её нет
setTimeout(() => {
db.get("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_user_settings_user_id'", (err, index) => {
if (err) {
console.error('❌ Ошибка проверки индекса:', err.message);
return;
}
if (!index) {
console.log('🔧 Создаем индекс для таблицы user_settings...');
db.run("CREATE INDEX IF NOT EXISTS idx_user_settings_user_id ON user_settings(user_id)", (createErr) => {
if (createErr) {
console.error('❌ Ошибка создания индекса:', createErr.message);
} else {
console.log('✅ Индекс для user_settings создан');
db.get("SELECT id FROM user_groups WHERE name = 'Секретарь'", (err, group) => {
if (err || !group) {
console.log('🔧 Создаем группу "Секретарь" по умолчанию...');
db.run(
`INSERT INTO user_groups (name, description, color, can_approve_documents)
VALUES ('Секретарь', 'Группа для согласования документов', '#e74c3c', 1)`,
(insertErr) => {
if (insertErr) {
console.error('❌ Ошибка создания группы "Секретарь":', insertErr.message);
} else {
console.log('✅ Группа "Секретарь" создана по умолчанию');
}
}
});
);
}
});
// Создаем индексы для новых таблиц
setTimeout(() => {
const newIndexes = [
"CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type)",
"CREATE INDEX IF NOT EXISTS idx_tasks_approver_group_id ON tasks(approver_group_id)",
"CREATE INDEX IF NOT EXISTS idx_user_groups_can_approve ON user_groups(can_approve_documents)",
"CREATE INDEX IF NOT EXISTS idx_user_group_memberships_user_id ON user_group_memberships(user_id)",
"CREATE INDEX IF NOT EXISTS idx_user_group_memberships_group_id ON user_group_memberships(group_id)",
"CREATE INDEX IF NOT EXISTS idx_simple_documents_task_id ON simple_documents(task_id)",
"CREATE INDEX IF NOT EXISTS idx_simple_documents_document_number ON simple_documents(document_number)"
];
newIndexes.forEach(indexQuery => {
db.run(indexQuery, (err) => {
if (err) {
console.error(`❌ Ошибка создания индекса: ${err.message}`);
} else {
console.log(`✅ Индекс создан: ${indexQuery}`);
}
});
});
// Создаем группу "Секретарь" по умолчанию, если её нет
db.get("SELECT id FROM user_groups WHERE name = 'Секретарь'", (err, group) => {
if (err || !group) {
console.log('🔧 Создаем группу "Секретарь" по умолчанию...');
db.run(
`INSERT INTO user_groups (name, description, color, can_approve_documents)
VALUES ('Секретарь', 'Группа для согласования документов', '#e74c3c', 1)`,
(insertErr) => {
if (insertErr) {
console.error('❌ Ошибка создания группы "Секретарь":', insertErr.message);
} else {
console.log('✅ Группа "Секретарь" создана по умолчанию');
}
}
);
}
});
}, 1000);
}, 1000);
}
@@ -728,7 +804,48 @@ async function createPostgresTables() {
console.log('🔧 Проверяем/создаем таблицы в PostgreSQL...');
// Создаем таблицы PostgreSQL
// Таблица для истории уведомлений
await client.query(`
CREATE TABLE IF NOT EXISTS notification_history (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
task_id INTEGER NOT NULL REFERENCES tasks(id),
notification_type VARCHAR(50) NOT NULL,
last_sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, task_id, notification_type)
)
`);
// Таблица очереди email
await client.query(`
CREATE TABLE IF NOT EXISTS email_queue (
id SERIAL PRIMARY KEY,
to_email VARCHAR(500) NOT NULL,
subject VARCHAR(500) NOT NULL,
html_content TEXT NOT NULL,
user_id INTEGER REFERENCES users(id),
task_id INTEGER REFERENCES tasks(id),
notification_type VARCHAR(50),
retry_count INTEGER DEFAULT 0,
status VARCHAR(50) DEFAULT 'pending',
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// Таблица настроек email
await client.query(`
CREATE TABLE IF NOT EXISTS email_settings (
setting_key VARCHAR(100) PRIMARY KEY,
setting_value TEXT,
spam_blocked_until TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// Основные таблицы
await client.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
@@ -815,7 +932,7 @@ async function createPostgresTables() {
)
`);
// Добавляем таблицу для пользовательских настроек
// Таблица для пользовательских настроек
await client.query(`
CREATE TABLE IF NOT EXISTS user_settings (
id SERIAL PRIMARY KEY,
@@ -831,9 +948,7 @@ async function createPostgresTables() {
)
`);
// ===== НОВЫЕ ТАБЛИЦЫ ДЛЯ ГРУПП ПОЛЬЗОВАТЕЛЕЙ =====
// Таблица для групп пользователей
// Таблицы для групп пользователей
await client.query(`
CREATE TABLE IF NOT EXISTS user_groups (
id SERIAL PRIMARY KEY,
@@ -846,7 +961,6 @@ async function createPostgresTables() {
)
`);
// Таблица для связи пользователей с группами
await client.query(`
CREATE TABLE IF NOT EXISTS user_group_memberships (
id SERIAL PRIMARY KEY,
@@ -857,7 +971,7 @@ async function createPostgresTables() {
)
`);
// Таблицы для документов (существующие)
// Таблицы для документов
await client.query(`
CREATE TABLE IF NOT EXISTS document_types (
id SERIAL PRIMARY KEY,
@@ -980,40 +1094,7 @@ async function createPostgresTables() {
console.log('✅ Все таблицы PostgreSQL созданы/проверены');
// Создаем индексы
const indexes = [
'CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by)',
'CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)',
'CREATE INDEX IF NOT EXISTS idx_tasks_closed_at ON tasks(closed_at)',
'CREATE INDEX IF NOT EXISTS idx_task_assignments_task_id ON task_assignments(task_id)',
'CREATE INDEX IF NOT EXISTS idx_task_assignments_user_id ON task_assignments(user_id)',
'CREATE INDEX IF NOT EXISTS idx_task_assignments_status ON task_assignments(status)',
'CREATE INDEX IF NOT EXISTS idx_task_files_task_id ON task_files(task_id)',
'CREATE INDEX IF NOT EXISTS idx_activity_logs_task_id ON activity_logs(task_id)',
'CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs(created_at)',
'CREATE INDEX IF NOT EXISTS idx_user_settings_user_id ON user_settings(user_id)',
// Новые индексы
'CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type)',
'CREATE INDEX IF NOT EXISTS idx_tasks_approver_group_id ON tasks(approver_group_id)',
'CREATE INDEX IF NOT EXISTS idx_user_groups_can_approve ON user_groups(can_approve_documents)',
'CREATE INDEX IF NOT EXISTS idx_user_group_memberships_user_id ON user_group_memberships(user_id)',
'CREATE INDEX IF NOT EXISTS idx_user_group_memberships_group_id ON user_group_memberships(group_id)',
// Индексы для документов
'CREATE INDEX IF NOT EXISTS idx_documents_status ON documents(status)',
'CREATE INDEX IF NOT EXISTS idx_documents_created_by ON documents(created_by)',
'CREATE INDEX IF NOT EXISTS idx_document_approvals_document_id ON document_approvals(document_id)',
'CREATE INDEX IF NOT EXISTS idx_document_approvals_status ON document_approvals(status)',
// Индексы для простых документов
'CREATE INDEX IF NOT EXISTS idx_simple_documents_task_id ON simple_documents(task_id)',
'CREATE INDEX IF NOT EXISTS idx_simple_documents_document_number ON simple_documents(document_number)'
];
for (const indexQuery of indexes) {
try {
await client.query(indexQuery);
} catch (err) {
console.warn(`⚠️ Не удалось создать индекс: ${err.message}`);
}
}
await createPostgresIndexes(client);
client.release();
console.log('✅ Таблицы PostgreSQL проверены/созданы');
@@ -1040,6 +1121,66 @@ async function createPostgresTables() {
}
}
async function createPostgresIndexes(client) {
console.log('🔧 Создаем индексы для PostgreSQL...');
const indexes = [
// Индексы для очереди email
'CREATE INDEX IF NOT EXISTS idx_email_queue_status ON email_queue(status, created_at)',
'CREATE INDEX IF NOT EXISTS idx_email_queue_user_id ON email_queue(user_id)',
'CREATE INDEX IF NOT EXISTS idx_email_queue_task_id ON email_queue(task_id)',
// Индексы для уведомлений
'CREATE INDEX IF NOT EXISTS idx_notification_history_user_id ON notification_history(user_id)',
'CREATE INDEX IF NOT EXISTS idx_notification_history_task_id ON notification_history(task_id)',
'CREATE INDEX IF NOT EXISTS idx_notification_history_type ON notification_history(notification_type)',
'CREATE INDEX IF NOT EXISTS idx_notification_history_sent_at ON notification_history(last_sent_at)',
// Индексы для пользователей
'CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)',
'CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)',
// Индексы для задач
'CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by)',
'CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)',
'CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type)',
'CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date)',
'CREATE INDEX IF NOT EXISTS idx_tasks_closed_at ON tasks(closed_at)',
// Индексы для назначений задач
'CREATE INDEX IF NOT EXISTS idx_task_assignments_task_id ON task_assignments(task_id)',
'CREATE INDEX IF NOT EXISTS idx_task_assignments_user_id ON task_assignments(user_id)',
'CREATE INDEX IF NOT EXISTS idx_task_assignments_status ON task_assignments(status)',
// Индексы для файлов
'CREATE INDEX IF NOT EXISTS idx_task_files_task_id ON task_files(task_id)',
// Индексы для логов активности
'CREATE INDEX IF NOT EXISTS idx_activity_logs_task_id ON activity_logs(task_id)',
'CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs(created_at)',
// Индексы для настроек пользователей
'CREATE INDEX IF NOT EXISTS idx_user_settings_user_id ON user_settings(user_id)',
// Индексы для групп
'CREATE INDEX IF NOT EXISTS idx_user_groups_can_approve ON user_groups(can_approve_documents)',
'CREATE INDEX IF NOT EXISTS idx_user_group_memberships_user_id ON user_group_memberships(user_id)',
'CREATE INDEX IF NOT EXISTS idx_user_group_memberships_group_id ON user_group_memberships(group_id)',
// Индексы для простых документов
'CREATE INDEX IF NOT EXISTS idx_simple_documents_task_id ON simple_documents(task_id)',
'CREATE INDEX IF NOT EXISTS idx_simple_documents_document_number ON simple_documents(document_number)'
];
for (const indexQuery of indexes) {
try {
await client.query(indexQuery);
} catch (err) {
console.warn(`⚠️ Не удалось создать индекс: ${err.message}`);
}
}
}
// Функция для проверки структуры таблиц PostgreSQL
async function checkPostgresTableStructure() {
if (!USE_POSTGRES) return;
@@ -1051,32 +1192,31 @@ async function checkPostgresTableStructure() {
// Определяем ожидаемую структуру таблиц PostgreSQL
const tableSchemas = {
user_settings: [
{ name: 'id', type: 'SERIAL PRIMARY KEY' },
{ name: 'user_id', type: 'INTEGER UNIQUE NOT NULL REFERENCES users(id)' },
{ name: 'email_notifications', type: 'BOOLEAN DEFAULT true' },
{ name: 'notification_email', type: 'TEXT' },
{ name: 'telegram_notifications', type: 'BOOLEAN DEFAULT false' },
{ name: 'telegram_chat_id', type: 'TEXT' },
{ name: 'vk_notifications', type: 'BOOLEAN DEFAULT false' },
{ name: 'vk_user_id', type: 'TEXT' },
email_queue: [
{ name: 'to_email', type: 'VARCHAR(500) NOT NULL' },
{ name: 'subject', type: 'VARCHAR(500) NOT NULL' },
{ name: 'html_content', type: 'TEXT NOT NULL' },
{ name: 'user_id', type: 'INTEGER REFERENCES users(id)' },
{ name: 'task_id', type: 'INTEGER REFERENCES tasks(id)' },
{ name: 'notification_type', type: 'VARCHAR(50)' },
{ name: 'retry_count', type: 'INTEGER DEFAULT 0' },
{ name: 'status', type: 'VARCHAR(50) DEFAULT \'pending\'' },
{ name: 'error_message', type: 'TEXT' },
{ name: 'created_at', type: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP' },
{ name: 'updated_at', type: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP' }
],
tasks: [
{ name: 'task_type', type: 'VARCHAR(50) DEFAULT "regular"' },
{ name: 'approver_group_id', type: 'INTEGER' },
{ name: 'document_id', type: 'INTEGER' }
email_settings: [
{ name: 'setting_key', type: 'VARCHAR(100) PRIMARY KEY' },
{ name: 'setting_value', type: 'TEXT' },
{ name: 'spam_blocked_until', type: 'TIMESTAMP' },
{ name: 'updated_at', type: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP' }
],
simple_documents: [
{ name: 'task_id', type: 'INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE' },
{ name: 'document_type_id', type: 'INTEGER REFERENCES simple_document_types(id)' },
{ name: 'document_number', type: 'VARCHAR(100)' },
{ name: 'document_date', type: 'DATE' },
{ name: 'pages_count', type: 'INTEGER' },
{ name: 'urgency_level', type: 'VARCHAR(20) CHECK (urgency_level IN (\'normal\', \'urgent\', \'very_urgent\'))' },
{ name: 'comment', type: 'TEXT' },
{ name: 'refusal_reason', type: 'TEXT' }
notification_history: [
{ name: 'user_id', type: 'INTEGER NOT NULL REFERENCES users(id)' },
{ name: 'task_id', type: 'INTEGER NOT NULL REFERENCES tasks(id)' },
{ name: 'notification_type', type: 'VARCHAR(50) NOT NULL' },
{ name: 'last_sent_at', type: 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP' },
{ name: 'created_at', type: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP' }
]
};