56 lines
3.2 KiB
MySQL
56 lines
3.2 KiB
MySQL
|
|
-- 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);
|