-- Создаем таблицу для отслеживания отправленных уведомлений об окончании подписки CREATE TABLE IF NOT EXISTS subscription_notifications ( id SERIAL PRIMARY KEY, parent_id BIGINT NOT NULL REFERENCES parents(user_id) ON DELETE CASCADE, notified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, days_left INTEGER NOT NULL, UNIQUE(parent_id, days_left) -- Чтобы не дублировать уведомления для одного количества дней ); -- Создаем таблицу справочника школ CREATE TABLE IF NOT EXISTS schools ( id SERIAL PRIMARY KEY, school_number VARCHAR(50) NOT NULL UNIQUE, school_name VARCHAR(255) NOT NULL, address TEXT, city VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Создаем таблицу родителей (пользователей) CREATE TABLE IF NOT EXISTS parents ( user_id BIGINT PRIMARY KEY, username VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Создаем таблицу детей CREATE TABLE IF NOT EXISTS children ( id SERIAL PRIMARY KEY, parent_id BIGINT NOT NULL REFERENCES parents(user_id) ON DELETE CASCADE, card_number VARCHAR(16) NOT NULL, student_name VARCHAR(255) NOT NULL, school_id INTEGER NOT NULL REFERENCES schools(id) ON DELETE CASCADE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(parent_id, card_number) ); -- Индексы для детей CREATE INDEX IF NOT EXISTS idx_children_parent_id ON children(parent_id); CREATE INDEX IF NOT EXISTS idx_children_card_number ON children(card_number); CREATE INDEX IF NOT EXISTS idx_children_school_id ON children(school_id); CREATE INDEX IF NOT EXISTS idx_subscription_notifications_parent_id ON subscription_notifications(parent_id); -- Создаем таблицу для истории проходов через турникет CREATE TABLE IF NOT EXISTS access_events ( id SERIAL PRIMARY KEY, event_id VARCHAR(100) UNIQUE, user_id VARCHAR(100) NOT NULL, device_id VARCHAR(100), resource_number INTEGER NOT NULL, access_zone_id1 VARCHAR(100), access_zone_id2 VARCHAR(100), school_id INTEGER REFERENCES schools(id) ON DELETE CASCADE, event_time TIMESTAMP NOT NULL, processed BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Индексы для access_events CREATE INDEX IF NOT EXISTS idx_access_events_user_id ON access_events(user_id); CREATE INDEX IF NOT EXISTS idx_access_events_time ON access_events(event_time); CREATE INDEX IF NOT EXISTS idx_access_events_processed ON access_events(processed); -- Создаем таблицу уведомлений CREATE TABLE IF NOT EXISTS notifications ( id SERIAL PRIMARY KEY, parent_id BIGINT NOT NULL REFERENCES parents(user_id) ON DELETE CASCADE, child_id INTEGER REFERENCES children(id) ON DELETE CASCADE, message TEXT NOT NULL, event_type VARCHAR(50), is_read BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Индексы для уведомлений CREATE INDEX IF NOT EXISTS idx_notifications_parent_id ON notifications(parent_id); CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications(is_read); -- ============================================ -- ПОДПИСОЧНАЯ СИСТЕМА -- ============================================ -- Создаем таблицу подписок CREATE TABLE IF NOT EXISTS subscriptions ( id SERIAL PRIMARY KEY, parent_id BIGINT NOT NULL UNIQUE REFERENCES parents(user_id) ON DELETE CASCADE, status VARCHAR(20) NOT NULL DEFAULT 'trial', -- 'trial', 'active', 'expired', 'cancelled' trial_start TIMESTAMP DEFAULT CURRENT_TIMESTAMP, trial_end TIMESTAMP, paid_until TIMESTAMP, last_payment_date TIMESTAMP, payment_amount DECIMAL(10, 2) DEFAULT 50.00, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Индексы для подписок CREATE INDEX IF NOT EXISTS idx_subscriptions_parent_id ON subscriptions(parent_id); CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status); -- Создаем таблицу истории платежей CREATE TABLE IF NOT EXISTS payments ( id SERIAL PRIMARY KEY, parent_id BIGINT NOT NULL REFERENCES parents(user_id) ON DELETE CASCADE, amount DECIMAL(10, 2) NOT NULL, payment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, payment_method VARCHAR(50), transaction_id VARCHAR(255) UNIQUE, status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'success', 'failed' created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Индексы для платежей CREATE INDEX IF NOT EXISTS idx_payments_parent_id ON payments(parent_id); CREATE INDEX IF NOT EXISTS idx_payments_transaction_id ON payments(transaction_id); CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status); -- ============================================ -- ФУНКЦИИ И ТРИГГЕРЫ -- ============================================ -- Функция для автоматического обновления updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ language 'plpgsql'; -- Триггер для обновления updated_at в parents DROP TRIGGER IF EXISTS update_parents_updated_at ON parents; CREATE TRIGGER update_parents_updated_at BEFORE UPDATE ON parents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Триггер для обновления updated_at в children DROP TRIGGER IF EXISTS update_children_updated_at ON children; CREATE TRIGGER update_children_updated_at BEFORE UPDATE ON children FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Триггер для обновления updated_at в subscriptions DROP TRIGGER IF EXISTS update_subscriptions_updated_at ON subscriptions; CREATE TRIGGER update_subscriptions_updated_at BEFORE UPDATE ON subscriptions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ============================================ -- НАЧАЛЬНЫЕ ДАННЫЕ -- ============================================ -- Добавляем школы INSERT INTO schools (school_number, school_name, address, city) VALUES ('101', 'Средняя общеобразовательная школа №101', 'ул. Ленина, 15', 'Москва'), ('102', 'Средняя общеобразовательная школа №102', 'ул. Гагарина, 10', 'Москва'), ('103', 'Средняя общеобразовательная школа №103', 'ул. Мира, 25', 'Москва'), ('104', 'Гимназия №104', 'ул. Садовая, 8', 'Москва'), ('105', 'Лицей №105', 'ул. Тверская, 12', 'Москва'), ('201', 'Гимназия №201', 'ул. Пушкина, 5', 'Санкт-Петербург'), ('202', 'Средняя школа №202', 'ул. Невский проспект, 20', 'Санкт-Петербург'), ('203', 'Средняя школа №203', 'ул. Московская, 15', 'Санкт-Петербург'), ('301', 'Лицей №301', 'ул. Советская, 8', 'Казань'), ('302', 'Средняя школа №302', 'ул. Ленина, 42', 'Казань'), ('401', 'Средняя школа №401', 'ул. Мира, 12', 'Новосибирск'), ('402', 'Гимназия №402', 'ул. Кирова, 7', 'Новосибирск'), ('501', 'Школа №501 с углубленным изучением математики', 'ул. Лермонтова, 3', 'Екатеринбург'), ('502', 'Гимназия №502', 'ул. Свердлова, 10', 'Екатеринбург'), ('601', 'Средняя общеобразовательная школа №601', 'ул. Садовая, 25', 'Нижний Новгород'), ('602', 'Лицей №602', 'ул. Горького, 8', 'Нижний Новгород'), ('701', 'Гимназия №701', 'ул. Чехова, 10', 'Ростов-на-Дону'), ('702', 'Средняя школа №702', 'ул. Пушкинская, 15', 'Ростов-на-Дону') ON CONFLICT (school_number) DO NOTHING; -- Добавляем тестового пользователя (для разработки) -- INSERT INTO parents (user_id, username) VALUES (15649081, 'Алексей Халецкий') -- ON CONFLICT (user_id) DO NOTHING; -- Добавляем тестовую подписку для разработчика (активная на год) -- INSERT INTO subscriptions (parent_id, status, paid_until, payment_amount) -- VALUES (15649081, 'active', CURRENT_TIMESTAMP + INTERVAL '365 days', 50.00) -- ON CONFLICT (parent_id) DO NOTHING; -- ============================================ -- ПРОВЕРОЧНЫЕ ЗАПРОСЫ -- ============================================ -- Проверка количества школ -- SELECT COUNT(*) as total_schools FROM schools; -- Проверка подписок -- SELECT -- p.username, -- s.status, -- s.trial_end, -- s.paid_until, -- s.payment_amount -- FROM subscriptions s -- JOIN parents p ON s.parent_id = p.user_id; -- Проверка детей -- SELECT -- p.username, -- c.student_name, -- c.card_number, -- sc.school_name -- FROM children c -- JOIN parents p ON c.parent_id = p.user_id -- JOIN schools sc ON c.school_id = sc.id;