axe/internal/db/migrations/0001_init.sql

130 lines
6.4 KiB
MySQL
Raw Normal View History

2026-10-18 19:07:25 +00:00
-- 0001_init — начальная схема.
--
-- Файл рассчитан на два случая:
-- * свежая БД — всё создаётся с нуля;
-- * БД, где таблицы были созданы вручную до появления миграций —
-- CREATE ... IF NOT EXISTS ничего не ломает, а блоки ALTER/DROP/ADD
-- приводят ограничения к эталону (атомарно, в одной транзакции).
--
-- После применения файл редактировать нельзя: раннер хранит checksum
-- и упадёт на старте. Любое изменение — это новая миграция 0002_*.sql.
-- ============ profiles ============
CREATE TABLE IF NOT EXISTS profiles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text NOT NULL,
password_hash text NOT NULL,
name text NOT NULL,
phone text,
role text NOT NULL DEFAULT 'client',
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT profiles_role_check CHECK (role IN ('client', 'worker', 'admin'))
);
CREATE UNIQUE INDEX IF NOT EXISTS profiles_email_key ON profiles (email);
CREATE INDEX IF NOT EXISTS profiles_role_idx ON profiles (role) WHERE is_active;
ALTER TABLE profiles DROP CONSTRAINT IF EXISTS profiles_role_check;
ALTER TABLE profiles ADD CONSTRAINT profiles_role_check
CHECK (role IN ('client', 'worker', 'admin'));
-- ============ tickets ============
CREATE TABLE IF NOT EXISTS tickets (
id bigserial PRIMARY KEY,
client_id uuid NOT NULL REFERENCES profiles (id),
worker_id uuid REFERENCES profiles (id),
assigned_by uuid REFERENCES profiles (id),
address text NOT NULL,
floor text,
category text NOT NULL,
description text NOT NULL,
priority text NOT NULL DEFAULT 'normal',
status text NOT NULL DEFAULT 'new',
created_at timestamptz NOT NULL DEFAULT now(),
assigned_at timestamptz,
started_at timestamptz,
done_at timestamptz,
confirmed_at timestamptz,
closed_at timestamptz,
CONSTRAINT tickets_category_check
CHECK (category IN ('electric', 'plumbing', 'climate', 'elevator', 'other')),
CONSTRAINT tickets_priority_check
CHECK (priority IN ('low', 'normal', 'high')),
-- 'rejected' оставлен из исходной ручной схемы, 'closed' добавлен,
-- чтобы поле closed_at имело свой статус.
CONSTRAINT tickets_status_check
CHECK (status IN ('new', 'assigned', 'in_progress', 'done',
'confirmed', 'rejected', 'closed')),
-- Ядро модели: свободная заявка ⇔ статус 'new'.
-- Назначение мастера всегда меняет worker_id и status одной операцией,
-- поэтому пул свободных заявок не «протекает» в другие статусы.
CONSTRAINT tickets_free_iff_new
CHECK ((worker_id IS NULL) = (status = 'new')),
-- Назначение фиксируется временем — иначе UI не покажет «когда назначили».
CONSTRAINT tickets_assigned_time
CHECK (worker_id IS NULL OR assigned_at IS NOT NULL),
-- Статусы, которых достигли, обязаны иметь свой таймстамп.
CONSTRAINT tickets_started_time
CHECK (status <> 'in_progress' OR started_at IS NOT NULL),
CONSTRAINT tickets_done_time
CHECK (status <> 'done' OR done_at IS NOT NULL),
CONSTRAINT tickets_confirmed_time
CHECK (status <> 'confirmed' OR confirmed_at IS NOT NULL),
CONSTRAINT tickets_closed_time
CHECK (status <> 'closed' OR closed_at IS NOT NULL)
);
-- Приводим ограничения к эталону, если таблица существовала до миграций.
-- DROP + ADD вместо IF NOT EXISTS: у ALTER TABLE ADD CONSTRAINT его нет.
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_category_check;
ALTER TABLE tickets ADD CONSTRAINT tickets_category_check
CHECK (category IN ('electric', 'plumbing', 'climate', 'elevator', 'other'));
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_priority_check;
ALTER TABLE tickets ADD CONSTRAINT tickets_priority_check
CHECK (priority IN ('low', 'normal', 'high'));
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_status_check;
ALTER TABLE tickets ADD CONSTRAINT tickets_status_check
CHECK (status IN ('new', 'assigned', 'in_progress', 'done',
'confirmed', 'rejected', 'closed'));
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_free_iff_new;
ALTER TABLE tickets ADD CONSTRAINT tickets_free_iff_new
CHECK ((worker_id IS NULL) = (status = 'new'));
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_assigned_time;
ALTER TABLE tickets ADD CONSTRAINT tickets_assigned_time
CHECK (worker_id IS NULL OR assigned_at IS NOT NULL);
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_started_time;
ALTER TABLE tickets ADD CONSTRAINT tickets_started_time
CHECK (status <> 'in_progress' OR started_at IS NOT NULL);
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_done_time;
ALTER TABLE tickets ADD CONSTRAINT tickets_done_time
CHECK (status <> 'done' OR done_at IS NOT NULL);
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_confirmed_time;
ALTER TABLE tickets ADD CONSTRAINT tickets_confirmed_time
CHECK (status <> 'confirmed' OR confirmed_at IS NOT NULL);
ALTER TABLE tickets DROP CONSTRAINT IF EXISTS tickets_closed_time;
ALTER TABLE tickets ADD CONSTRAINT tickets_closed_time
CHECK (status <> 'closed' OR closed_at IS NOT NULL);
-- Ленты: клиент — свои, мастер — назначенные ему, админ/дашборд — по статусу.
-- Имена совпадают с уже существующими в ручной схеме, чтобы не плодить дубли.
CREATE INDEX IF NOT EXISTS idx_tickets_client ON tickets (client_id);
CREATE INDEX IF NOT EXISTS idx_tickets_worker ON tickets (worker_id);
CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets (status);
CREATE INDEX IF NOT EXISTS idx_tickets_created_at ON tickets (created_at DESC);