85 lines
4.7 KiB
SQL
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;
|