-- 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);