axe/internal/db/migrations/0006_ticket_events.sql

56 lines
3.2 KiB
MySQL
Raw Normal View History

2026-10-18 19:07:25 +00:00
-- 0006_ticket_events — журнал событий заявки.
--
-- Каждый переход статусной машины оставляет запись: кто сделал, что было,
-- что стало, причина (для rejected/canceled) и когда. Запись создаётся
-- **в той же SQL-операции**, что и сам переход (CTE: UPDATE … RETURNING
-- → INSERT в журнал), поэтому «заявка перешла, а истории нет» невозможно:
-- либо обе части, либо ни одной.
--
-- from_status NULL — только у события «создана»: заявка появилась
-- из ничего. to_status всегда заполнен. actor_id может стать NULL
-- при удалении аккаунта — история не пропадает, остаётся «кто-то».
--
-- Действия: created, assigned, started, done, confirmed, rejected, canceled.
-- Названия действий отделены от статусов: одинаковый to_status='closed'
-- дают и принятие клиента (confirmed), и отмена (canceled) — журналу
-- важно именно «что сделали», а не только «куда ушло».
--
-- Старые заявки (до этой миграции) журнала не имеют — история начинается
-- с момента наката.
--
-- Применённый файл не редактировать (checksum в schema_migrations).
CREATE TABLE ticket_events (
id bigserial PRIMARY KEY,
ticket_id bigint NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
-- NULL — аккаунт автора удалён (ON DELETE SET NULL)
actor_id uuid REFERENCES profiles(id) ON DELETE SET NULL,
action text NOT NULL,
from_status text,
to_status text NOT NULL,
-- причина отклонения/отмены, для остальных действий пусто
comment text,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT ticket_events_action_check
CHECK (action IN ('created', 'assigned', 'started', 'done',
'confirmed', 'rejected', 'canceled')),
CONSTRAINT ticket_events_from_status_check
CHECK (from_status IS NULL OR from_status IN
('new', 'assigned', 'in_progress', 'done', 'confirmed',
'rejected', 'closed')),
CONSTRAINT ticket_events_to_status_check
CHECK (to_status IN
('new', 'assigned', 'in_progress', 'done', 'confirmed',
'rejected', 'closed')),
-- причина нужна ровно там, где её требует машина статусов
CONSTRAINT ticket_events_comment_shape
CHECK (action NOT IN ('rejected', 'canceled')
OR comment IS NOT NULL)
);
-- Журнал читается по заявке в хронологии: (ticket_id, id) покрывает
-- и выборку страницы, и сортировку по умолчанию.
CREATE INDEX IF NOT EXISTS idx_ticket_events_ticket
ON ticket_events (ticket_id, id);