Files
neon-bar86.ru/crm/migrate/notes_boards.sql
T
2026-10-11 22:24:54 +05:00

85 lines
4.7 KiB
SQL

-- -*- coding: utf-8 -*-
-- ============================================================================
-- НЕОН CRM — доски заметок/задач (общая + приватные «владелец ↔ админ»).
--
-- Идемпотентна: повторный запуск не падает и не меняет данные.
-- CRM применяет этот же DDL автоматически при старте (SCHEMA в storage.py,
-- сид общей доски — boards_seed_shared()); файл нужен для ручного применения
-- и как канонический список объектов.
--
-- Применить вручную:
-- docker compose exec -T db psql -U neon -d neon_crm \
-- -v ON_ERROR_STOP=1 < crm/migrate/notes_boards.sql
--
-- Откат (теряются все заметки и история!):
-- docker compose exec -T db psql -U neon -d neon_crm \
-- -c "DROP TABLE IF EXISTS note_activity, notes, board_members, boards CASCADE"
--
-- Отступление от исходного ТЗ: пользователи в этой базе — crm_users с текстовым
-- PK login (целочисленного users.id не существует), поэтому все ссылки идут
-- на crm_users(login).
-- ============================================================================
CREATE TABLE IF NOT EXISTS boards (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kind TEXT NOT NULL CHECK (kind IN ('shared', 'private')),
title TEXT NOT NULL,
created_by_login TEXT NOT NULL REFERENCES crm_users(login),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- участники приватных досок; для kind='shared' не используется —
-- общая доска доступна всем ролям без проверки
CREATE TABLE IF NOT EXISTS board_members (
board_id INTEGER NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
user_login TEXT NOT NULL REFERENCES crm_users(login) ON DELETE CASCADE,
PRIMARY KEY (board_id, user_login)
);
-- заметка = задача: заголовок, текст, исполнитель, срок, «важно», статус.
-- К сменам не привязана — доступна в любое время по факту логина.
-- Логины автора/исполнителя без FK (как orders.waiter в storage.py):
-- удаление сотрудника не должно ломать его задачи.
CREATE TABLE IF NOT EXISTS notes (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
board_id INTEGER NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
author_login TEXT NOT NULL,
assignee_login TEXT,
title TEXT NOT NULL,
body TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'in_progress', 'done')),
important BOOLEAN NOT NULL DEFAULT FALSE,
due_date DATE,
done_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_notes_board ON notes (board_id, status);
CREATE INDEX IF NOT EXISTS idx_notes_assignee ON notes (assignee_login) WHERE status <> 'done';
-- журнал действий: created/status/assigned/edited/deleted/board_created.
-- details: для assigned — логин исполнителя, для deleted — id заметки.
-- note_id REFERENCES ... ON DELETE SET NULL: сама запись переживает удаление
-- заметки, id удалённой сохраняется в details (для синхронизации клиентов).
-- user_login без FK — журнал переживает удаление сотрудника.
CREATE TABLE IF NOT EXISTS note_activity (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
board_id INTEGER NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
note_id BIGINT REFERENCES notes(id) ON DELETE SET NULL,
user_login TEXT NOT NULL,
action TEXT NOT NULL,
details TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- одна общая доска на базу; владелец = первый по логину owner (база после
-- seed CRM уже содержит учётки). Повторный запуск ничего не добавляет.
INSERT INTO boards (kind, title, created_by_login)
SELECT 'shared', 'Общая', u.login
FROM crm_users u
WHERE u.role = 'owner'
AND NOT EXISTS (SELECT 1 FROM boards WHERE kind = 'shared')
ORDER BY u.login
LIMIT 1;