920 lines
48 KiB
MySQL
920 lines
48 KiB
MySQL
|
|
-- Создание базы данных (выполните один раз под суперпользователем postgres):
|
|||
|
|
-- CREATE DATABASE mkd_control_center WITH ENCODING 'UTF8';
|
|||
|
|
|
|||
|
|
-- Далее подключитесь к базе mkd_control_center и выполните это тело файла.
|
|||
|
|
|
|||
|
|
-- ========= УЧАСТКИ =========
|
|||
|
|
CREATE TABLE IF NOT EXISTS districts (
|
|||
|
|
id VARCHAR(50) PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
manager_name TEXT NOT NULL
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- ========= ДОМА =========
|
|||
|
|
-- Для упрощения: вся структура Building хранится в одном JSONB-поле data.
|
|||
|
|
CREATE TABLE IF NOT EXISTS buildings (
|
|||
|
|
id VARCHAR(50) PRIMARY KEY,
|
|||
|
|
data JSONB NOT NULL
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- ========= ЗАЯВКИ =========
|
|||
|
|
CREATE TYPE doma_application_status AS ENUM ('new', 'in_progress', 'deferred', 'done', 'canceled');
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS applications (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
number TEXT NOT NULL,
|
|||
|
|
status doma_application_status NOT NULL,
|
|||
|
|
description TEXT NOT NULL,
|
|||
|
|
address TEXT NOT NULL,
|
|||
|
|
apartment TEXT NOT NULL,
|
|||
|
|
client_name TEXT NOT NULL,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
deadline_at TIMESTAMPTZ NOT NULL,
|
|||
|
|
performer_name TEXT,
|
|||
|
|
employee_id VARCHAR(50) REFERENCES employees(id) ON DELETE SET NULL,
|
|||
|
|
building_id VARCHAR(50) REFERENCES buildings(id) ON DELETE SET NULL,
|
|||
|
|
is_overdue BOOLEAN NOT NULL DEFAULT FALSE,
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_applications_building ON applications(building_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_applications_employee ON applications(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_applications_performer ON applications(performer_name);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_applications_status ON applications(status);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_applications_overdue ON applications(is_overdue);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_applications_deadline ON applications(deadline_at);
|
|||
|
|
|
|||
|
|
-- ========= ТЕСТОВЫЕ ДАННЫЕ =========
|
|||
|
|
-- Демо данные удалены. Создавайте участки и дома через интерфейс приложения.
|
|||
|
|
|
|||
|
|
-- INSERT INTO districts (id, name, manager_name) VALUES
|
|||
|
|
-- ('d-1', 'Участок №1 (Центральный)', 'Смирнов А.В.'),
|
|||
|
|
-- ('d-2', 'Участок №2 (Заречный)', 'Петров Б.Г.')
|
|||
|
|
-- ON CONFLICT (id) DO NOTHING;
|
|||
|
|
|
|||
|
|
-- INSERT INTO buildings (id, data) VALUES ...
|
|||
|
|
-- ON CONFLICT (id) DO NOTHING;
|
|||
|
|
|
|||
|
|
-- INSERT INTO applications (number, status, description, address, apartment, client_name, created_at, deadline_at, performer_name)
|
|||
|
|
-- VALUES ...
|
|||
|
|
-- ON CONFLICT DO NOTHING;
|
|||
|
|
|
|||
|
|
-- ========= ФИНАНСОВЫЕ ДАННЫЕ ИЗ 1С =========
|
|||
|
|
|
|||
|
|
-- Настройки маппинга колонок CSV/XLSX (создаем первой, т.к. на неё ссылается financial_reports)
|
|||
|
|
CREATE TABLE IF NOT EXISTS financial_report_mappings (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
description TEXT,
|
|||
|
|
is_default BOOLEAN DEFAULT FALSE,
|
|||
|
|
column_mappings JSONB NOT NULL, -- { "source_column": "target_field", ... }
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- Метаданные загруженных отчетов
|
|||
|
|
CREATE TABLE IF NOT EXISTS financial_reports (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
filename TEXT NOT NULL,
|
|||
|
|
file_type VARCHAR(10) NOT NULL CHECK (file_type IN ('CSV', 'XLSX')),
|
|||
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
uploaded_by TEXT,
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'processing' CHECK (status IN ('processing', 'completed', 'failed', 'partial')),
|
|||
|
|
total_rows INTEGER,
|
|||
|
|
processed_rows INTEGER DEFAULT 0,
|
|||
|
|
error_rows INTEGER DEFAULT 0,
|
|||
|
|
error_log JSONB,
|
|||
|
|
mapping_id BIGINT REFERENCES financial_report_mappings(id) ON DELETE SET NULL
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- Финансовые данные по домам
|
|||
|
|
CREATE TABLE IF NOT EXISTS building_financial_data (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
building_id VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
|
|||
|
|
report_id BIGINT REFERENCES financial_reports(id) ON DELETE SET NULL,
|
|||
|
|
period_start DATE NOT NULL,
|
|||
|
|
period_end DATE NOT NULL,
|
|||
|
|
period_type VARCHAR(10) NOT NULL CHECK (period_type IN ('month', 'quarter', 'year')),
|
|||
|
|
-- Доходы
|
|||
|
|
total_income NUMERIC(15, 2) DEFAULT 0,
|
|||
|
|
income_by_items JSONB, -- { "item_name": amount, ... }
|
|||
|
|
-- Расходы
|
|||
|
|
total_expenses NUMERIC(15, 2) DEFAULT 0,
|
|||
|
|
expenses_by_items JSONB, -- { "item_name": amount, ... }
|
|||
|
|
-- Баланс
|
|||
|
|
balance NUMERIC(15, 2) DEFAULT 0,
|
|||
|
|
-- Дополнительные данные
|
|||
|
|
metadata JSONB,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(building_id, period_start, period_end, period_type)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_building_financial_data_building ON building_financial_data(building_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_building_financial_data_period ON building_financial_data(period_start, period_end);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_building_financial_data_report ON building_financial_data(report_id);
|
|||
|
|
|
|||
|
|
-- ========= ОФИС: ЗАЯВКИ НА РЕМОНТ ТЕХНИКИ =========
|
|||
|
|
|
|||
|
|
CREATE TYPE office_equipment_type AS ENUM ('pc', 'laptop', 'air_conditioner', 'printer', 'other');
|
|||
|
|
CREATE TYPE repair_request_status AS ENUM (
|
|||
|
|
'new',
|
|||
|
|
'search_contractor', -- Поиск подрядчика
|
|||
|
|
'agreed_with_contractor', -- Договорились с подрядчиком
|
|||
|
|
'waiting_delivery', -- Ожидание поставки
|
|||
|
|
'taken_for_repair', -- Увезли на ремонт
|
|||
|
|
'self_repair', -- Ремонт самостоятельно
|
|||
|
|
'in_progress', -- В работе
|
|||
|
|
'completed', -- Выполнена
|
|||
|
|
'canceled' -- Отменена
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- Справочник оборудования
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_equipment (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
type office_equipment_type NOT NULL,
|
|||
|
|
brand TEXT,
|
|||
|
|
model TEXT,
|
|||
|
|
serial_number TEXT UNIQUE,
|
|||
|
|
assigned_to TEXT, -- Имя сотрудника
|
|||
|
|
purchase_date DATE,
|
|||
|
|
warranty_until DATE,
|
|||
|
|
next_maintenance_date DATE, -- Дата следующего ТО
|
|||
|
|
condition VARCHAR(20) NOT NULL DEFAULT 'good' CHECK (condition IN ('good', 'fair', 'poor')),
|
|||
|
|
notes TEXT,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- Заявки на ремонт
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_repair_requests (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
equipment_id BIGINT NOT NULL REFERENCES office_equipment(id) ON DELETE CASCADE,
|
|||
|
|
requester_name TEXT NOT NULL,
|
|||
|
|
description TEXT NOT NULL,
|
|||
|
|
priority VARCHAR(10) NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
|
|||
|
|
status repair_request_status NOT NULL DEFAULT 'new',
|
|||
|
|
assigned_to TEXT, -- Исполнитель
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
started_at TIMESTAMPTZ,
|
|||
|
|
completed_at TIMESTAMPTZ,
|
|||
|
|
canceled_at TIMESTAMPTZ,
|
|||
|
|
cancel_reason TEXT,
|
|||
|
|
solution TEXT, -- Решение/что было сделано
|
|||
|
|
expected_return_date DATE, -- Ожидаемая дата возврата из ремонта
|
|||
|
|
attachments TEXT[], -- Пути к файлам (фото проблемы и т.д.)
|
|||
|
|
comments JSONB, -- [{ "author": "name", "text": "comment", "created_at": "timestamp" }]
|
|||
|
|
is_paid BOOLEAN DEFAULT FALSE, -- Платный ремонт
|
|||
|
|
cost NUMERIC(10, 2) DEFAULT 0, -- Стоимость ремонта
|
|||
|
|
cost_estimated BOOLEAN DEFAULT FALSE, -- Стоимость предварительная (требуется диагностика)
|
|||
|
|
invoice_id BIGINT, -- ID счета на оплату из финансов (если создан)
|
|||
|
|
invoice_url TEXT, -- Ссылка на счет
|
|||
|
|
waiting_delivery_deadline TEXT, -- Примерный срок при ожидание поставки
|
|||
|
|
waiting_delivery_contacts TEXT, -- Контакты для уточнения поставки
|
|||
|
|
taken_for_repair_deadline TEXT, -- Срок при увезли на ремонт
|
|||
|
|
taken_for_repair_contacts TEXT, -- Контакты при увезли на ремонт
|
|||
|
|
agreed_contractor_price NUMERIC(10, 2) -- Цена при договорились с подрядчиком
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_repair_requests_equipment ON office_repair_requests(equipment_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_repair_requests_status ON office_repair_requests(status);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_repair_requests_created ON office_repair_requests(created_at DESC);
|
|||
|
|
|
|||
|
|
-- История перемещений и событий по оборудованию
|
|||
|
|
CREATE TYPE office_equipment_history_type AS ENUM ('purchase', 'issue', 'transfer', 'repair', 'write_off');
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_equipment_history (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
equipment_id BIGINT NOT NULL REFERENCES office_equipment(id) ON DELETE CASCADE,
|
|||
|
|
event_type office_equipment_history_type NOT NULL,
|
|||
|
|
event_date DATE NOT NULL,
|
|||
|
|
assigned_to TEXT, -- кому выдано / кому передано (для issue, transfer)
|
|||
|
|
assigned_from TEXT, -- от кого (для transfer)
|
|||
|
|
reason TEXT, -- причина (для repair, write_off)
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_equipment_history_equipment ON office_equipment_history(equipment_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_equipment_history_date ON office_equipment_history(event_date DESC);
|
|||
|
|
|
|||
|
|
-- ========= ОФИС: БАЗА ЗНАНИЙ =========
|
|||
|
|
|
|||
|
|
-- Категории базы знаний
|
|||
|
|
CREATE TABLE IF NOT EXISTS knowledge_base_categories (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
slug TEXT NOT NULL UNIQUE,
|
|||
|
|
parent_id BIGINT REFERENCES knowledge_base_categories(id) ON DELETE CASCADE,
|
|||
|
|
description TEXT,
|
|||
|
|
icon TEXT,
|
|||
|
|
sort_order INTEGER DEFAULT 0,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_kb_categories_parent ON knowledge_base_categories(parent_id);
|
|||
|
|
|
|||
|
|
-- Статьи базы знаний
|
|||
|
|
CREATE TABLE IF NOT EXISTS knowledge_base_articles (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
title TEXT NOT NULL,
|
|||
|
|
slug TEXT NOT NULL UNIQUE,
|
|||
|
|
category_id BIGINT REFERENCES knowledge_base_categories(id) ON DELETE SET NULL,
|
|||
|
|
content TEXT NOT NULL, -- Markdown или HTML
|
|||
|
|
content_type VARCHAR(10) NOT NULL DEFAULT 'markdown' CHECK (content_type IN ('markdown', 'html')),
|
|||
|
|
author TEXT NOT NULL,
|
|||
|
|
version INTEGER NOT NULL DEFAULT 1,
|
|||
|
|
parent_version_id BIGINT REFERENCES knowledge_base_articles(id) ON DELETE SET NULL, -- Для версионирования
|
|||
|
|
is_published BOOLEAN DEFAULT TRUE,
|
|||
|
|
tags TEXT[],
|
|||
|
|
attachments TEXT[], -- Пути к прикрепленным файлам
|
|||
|
|
view_count INTEGER DEFAULT 0,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
published_at TIMESTAMPTZ
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_kb_articles_category ON knowledge_base_articles(category_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_kb_articles_slug ON knowledge_base_articles(slug);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_kb_articles_published ON knowledge_base_articles(is_published, published_at DESC);
|
|||
|
|
|
|||
|
|
-- ========= ОФИС: СОВЕЩАНИЯ И ПЕРЕГОВОРНЫЕ =========
|
|||
|
|
|
|||
|
|
-- Переговорные комнаты
|
|||
|
|
CREATE TABLE IF NOT EXISTS meeting_rooms (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
capacity INTEGER NOT NULL,
|
|||
|
|
location TEXT,
|
|||
|
|
equipment TEXT[], -- ['projector', 'whiteboard', 'video_conference']
|
|||
|
|
description TEXT,
|
|||
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- Совещания
|
|||
|
|
CREATE TABLE IF NOT EXISTS meetings (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
title TEXT NOT NULL,
|
|||
|
|
description TEXT,
|
|||
|
|
organizer TEXT NOT NULL,
|
|||
|
|
start_time TIMESTAMPTZ NOT NULL,
|
|||
|
|
end_time TIMESTAMPTZ NOT NULL,
|
|||
|
|
room_id BIGINT REFERENCES meeting_rooms(id) ON DELETE SET NULL,
|
|||
|
|
participants TEXT[] NOT NULL, -- Список участников
|
|||
|
|
agenda TEXT, -- Повестка дня
|
|||
|
|
notes TEXT, -- Заметки/протокол
|
|||
|
|
conclusions TEXT, -- Заключения совещания
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'in_progress', 'completed', 'canceled')),
|
|||
|
|
reminder_sent BOOLEAN DEFAULT FALSE,
|
|||
|
|
reminder_time TIMESTAMPTZ, -- Когда отправлять напоминание
|
|||
|
|
attachments TEXT[], -- Прикрепленные файлы
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
CHECK (end_time > start_time)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_meetings_room ON meetings(room_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_meetings_time ON meetings(start_time, end_time);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_meetings_status ON meetings(status);
|
|||
|
|
|
|||
|
|
-- Бронирования переговорных (отдельная таблица для истории)
|
|||
|
|
CREATE TABLE IF NOT EXISTS meeting_bookings (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
room_id BIGINT NOT NULL REFERENCES meeting_rooms(id) ON DELETE CASCADE,
|
|||
|
|
meeting_id BIGINT REFERENCES meetings(id) ON DELETE CASCADE,
|
|||
|
|
booked_by TEXT NOT NULL,
|
|||
|
|
start_time TIMESTAMPTZ NOT NULL,
|
|||
|
|
end_time TIMESTAMPTZ NOT NULL,
|
|||
|
|
purpose TEXT,
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'completed', 'canceled')),
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
CHECK (end_time > start_time)
|
|||
|
|
-- Примечание: проверка на пересечение времени выполняется на уровне приложения
|
|||
|
|
-- в API endpoint для предотвращения конфликтов бронирований
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_meeting_bookings_room ON meeting_bookings(room_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_meeting_bookings_time ON meeting_bookings(start_time, end_time);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_meeting_bookings_status ON meeting_bookings(status);
|
|||
|
|
|
|||
|
|
-- ========= ОФИС: ЗАЯВКИ НА ТМЦ (КАНЦТОВАРЫ, ХОЗ. НУЖДЫ) =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_supply_requests (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
requester_name TEXT NOT NULL, -- Сотрудник, создавший заявку
|
|||
|
|
category VARCHAR(50) NOT NULL, -- 'stationery', 'household', 'food', 'other'
|
|||
|
|
item_name TEXT NOT NULL,
|
|||
|
|
quantity INTEGER DEFAULT 1,
|
|||
|
|
issued_quantity INTEGER DEFAULT 0, -- Количество выданного сотруднику
|
|||
|
|
unit VARCHAR(20) DEFAULT 'шт.',
|
|||
|
|
amount NUMERIC(10, 2) DEFAULT 0,
|
|||
|
|
priority VARCHAR(10) NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'new' CHECK (status IN ('new', 'approved', 'ordered', 'received', 'canceled', 'archived', 'collected')),
|
|||
|
|
approved_by TEXT, -- Кто одобрил
|
|||
|
|
approved_at TIMESTAMPTZ,
|
|||
|
|
ordered_at TIMESTAMPTZ,
|
|||
|
|
received_at TIMESTAMPTZ,
|
|||
|
|
notes TEXT,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_supply_requests_status ON office_supply_requests(status);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_supply_requests_requester ON office_supply_requests(requester_name);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_supply_requests_created ON office_supply_requests(created_at DESC);
|
|||
|
|
|
|||
|
|
-- ========= ОФИС: ЗАКАЗЫ =========
|
|||
|
|
-- Заказы создаются из заявок на ТМЦ, могут включать несколько заявок
|
|||
|
|
-- Заказ может ждать счет, иметь несколько предложений от поставщиков
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_orders (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
order_number TEXT UNIQUE NOT NULL, -- Номер заказа (ORD-YYYY-MMDD-XXXX)
|
|||
|
|
title TEXT NOT NULL, -- Название заказа
|
|||
|
|
description TEXT, -- Описание заказа
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'waiting_quote', 'quotes_received', 'approved', 'ordered', 'received', 'canceled')),
|
|||
|
|
total_amount NUMERIC(10, 2) DEFAULT 0, -- Общая сумма заказа
|
|||
|
|
supplier_name TEXT, -- Название поставщика
|
|||
|
|
supplier_contact TEXT, -- Контакты поставщика
|
|||
|
|
invoice_id BIGINT, -- ID счета на оплату из финансов (если создан)
|
|||
|
|
invoice_url TEXT, -- Ссылка на счет
|
|||
|
|
expected_date DATE, -- Ожидаемая дата получения
|
|||
|
|
received_date DATE, -- Дата получения
|
|||
|
|
notes TEXT,
|
|||
|
|
created_by TEXT NOT NULL, -- Кто создал заказ
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- Связь заказов с заявками (многие ко многим)
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_order_items (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
order_id BIGINT NOT NULL REFERENCES office_orders(id) ON DELETE CASCADE,
|
|||
|
|
request_id BIGINT NOT NULL REFERENCES office_supply_requests(id) ON DELETE CASCADE,
|
|||
|
|
quantity INTEGER DEFAULT 1, -- Количество в заказе (может отличаться от заявки)
|
|||
|
|
unit_price NUMERIC(10, 2) DEFAULT 0, -- Цена за единицу в заказе
|
|||
|
|
total_price NUMERIC(10, 2) DEFAULT 0, -- Общая цена позиции
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(order_id, request_id)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
-- Предложения от поставщиков для заказа
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_order_quotes (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
order_id BIGINT NOT NULL REFERENCES office_orders(id) ON DELETE CASCADE,
|
|||
|
|
supplier_name TEXT NOT NULL,
|
|||
|
|
supplier_contact TEXT,
|
|||
|
|
total_amount NUMERIC(10, 2) NOT NULL,
|
|||
|
|
quote_file_url TEXT, -- Файл с коммерческим предложением
|
|||
|
|
notes TEXT,
|
|||
|
|
is_selected BOOLEAN DEFAULT FALSE, -- Выбранное предложение
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_orders_status ON office_orders(status);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_orders_created ON office_orders(created_at DESC);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_order_items_order ON office_order_items(order_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_order_items_request ON office_order_items(request_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_order_quotes_order ON office_order_quotes(order_id);
|
|||
|
|
|
|||
|
|
-- ========= ОФИС: СКЛАД =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_inventory (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
category VARCHAR(50), -- 'stationery', 'household', 'food', 'other'
|
|||
|
|
quantity NUMERIC(10, 2) NOT NULL DEFAULT 0,
|
|||
|
|
unit VARCHAR(20) NOT NULL DEFAULT 'шт.',
|
|||
|
|
min_threshold NUMERIC(10, 2) NOT NULL DEFAULT 0,
|
|||
|
|
last_restock DATE,
|
|||
|
|
last_restock_by TEXT, -- Кто пополнил
|
|||
|
|
location TEXT, -- Место хранения
|
|||
|
|
notes TEXT,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_office_inventory_category ON office_inventory(category);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_office_inventory_name ON office_inventory(name);
|
|||
|
|
|
|||
|
|
-- ========= ОФИС: ДОКУМЕНТООБОРОТ =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS office_documents (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
reg_number TEXT NOT NULL UNIQUE, -- Регистрационный номер
|
|||
|
|
title TEXT NOT NULL,
|
|||
|
|
correspondent TEXT NOT NULL, -- Корреспондент
|
|||
|
|
document_type VARCHAR(20) NOT NULL CHECK (document_type IN ('incoming', 'outgoing')),
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'registered' CHECK (status IN ('registered', 'processed', 'sent', 'archived')),
|
|||
|
|
date DATE NOT NULL,
|
|||
|
|
assigned_to TEXT, -- Кому назначен
|
|||
|
|
tracking_number TEXT, -- Трек-номер для отправленных
|
|||
|
|
file_url TEXT, -- Путь к файлу
|
|||
|
|
notes TEXT,
|
|||
|
|
created_by TEXT NOT NULL, -- Кто создал/зарегистрировал
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_office_documents_reg_number ON office_documents(reg_number);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_office_documents_status ON office_documents(status);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_office_documents_type ON office_documents(document_type);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_office_documents_date ON office_documents(date DESC);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_office_documents_created_by ON office_documents(created_by);
|
|||
|
|
|
|||
|
|
-- ========= HR: СОТРУДНИКИ =========
|
|||
|
|
|
|||
|
|
CREATE TYPE employee_status AS ENUM ('active', 'vacation', 'inactive');
|
|||
|
|
CREATE TYPE messenger_type AS ENUM ('Max', 'Telegram');
|
|||
|
|
|
|||
|
|
-- Основная таблица сотрудников
|
|||
|
|
CREATE TABLE IF NOT EXISTS employees (
|
|||
|
|
id VARCHAR(50) PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL, -- ФИО
|
|||
|
|
position TEXT NOT NULL, -- Должность
|
|||
|
|
phone TEXT NOT NULL, -- Номер телефона
|
|||
|
|
status employee_status NOT NULL DEFAULT 'active',
|
|||
|
|
salary NUMERIC(10, 2) NOT NULL,
|
|||
|
|
assigned_district_id VARCHAR(50) REFERENCES districts(id) ON DELETE SET NULL,
|
|||
|
|
manager_id VARCHAR(50) REFERENCES employees(id) ON DELETE SET NULL, -- Руководитель сотрудника
|
|||
|
|
birth_date DATE,
|
|||
|
|
photo_url TEXT,
|
|||
|
|
registration_date DATE,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_employees_district ON employees(assigned_district_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_employees_status ON employees(status);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_employees_manager ON employees(manager_id);
|
|||
|
|
|
|||
|
|
-- ========= СТАТИСТИКА ПРОИЗВОДИТЕЛЬНОСТИ СОТРУДНИКОВ =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_performance_stats (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_name TEXT NOT NULL,
|
|||
|
|
period_start DATE NOT NULL,
|
|||
|
|
period_end DATE NOT NULL,
|
|||
|
|
-- Статистика по заявкам
|
|||
|
|
total_assigned INTEGER NOT NULL DEFAULT 0, -- Всего заявок назначено
|
|||
|
|
total_completed INTEGER NOT NULL DEFAULT 0, -- Выполнено заявок
|
|||
|
|
total_overdue INTEGER NOT NULL DEFAULT 0, -- Просрочено заявок
|
|||
|
|
total_in_progress INTEGER NOT NULL DEFAULT 0, -- В работе
|
|||
|
|
total_deferred INTEGER NOT NULL DEFAULT 0, -- Отложено
|
|||
|
|
-- Расчётные показатели
|
|||
|
|
completion_rate NUMERIC(5, 2) NOT NULL DEFAULT 0, -- Процент выполнения (с учётом общего контекста)
|
|||
|
|
overdue_rate NUMERIC(5, 2) NOT NULL DEFAULT 0, -- Процент просрочек
|
|||
|
|
performance_score NUMERIC(5, 2) NOT NULL DEFAULT 0, -- Общий рейтинг производительности (0-100)
|
|||
|
|
-- Связи
|
|||
|
|
district_id VARCHAR(50) REFERENCES districts(id) ON DELETE SET NULL,
|
|||
|
|
-- Метаданные
|
|||
|
|
calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(employee_name, period_start, period_end)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_performance_stats_employee ON employee_performance_stats(employee_name);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_performance_stats_district ON employee_performance_stats(district_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_performance_stats_period ON employee_performance_stats(period_start, period_end);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_performance_stats_score ON employee_performance_stats(performance_score DESC);
|
|||
|
|
|
|||
|
|
-- ========= СТАТИСТИКА ПО УЧАСТКАМ =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS district_performance_stats (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
district_id VARCHAR(50) NOT NULL REFERENCES districts(id) ON DELETE CASCADE,
|
|||
|
|
period_start DATE NOT NULL,
|
|||
|
|
period_end DATE NOT NULL,
|
|||
|
|
-- Статистика по заявкам
|
|||
|
|
total_applications INTEGER NOT NULL DEFAULT 0,
|
|||
|
|
total_completed INTEGER NOT NULL DEFAULT 0,
|
|||
|
|
total_overdue INTEGER NOT NULL DEFAULT 0,
|
|||
|
|
total_in_progress INTEGER NOT NULL DEFAULT 0,
|
|||
|
|
-- Расчётные показатели
|
|||
|
|
completion_rate NUMERIC(5, 2) NOT NULL DEFAULT 0,
|
|||
|
|
overdue_rate NUMERIC(5, 2) NOT NULL DEFAULT 0,
|
|||
|
|
average_score NUMERIC(5, 2) NOT NULL DEFAULT 0, -- Средний рейтинг сотрудников участка
|
|||
|
|
-- Метаданные
|
|||
|
|
calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(district_id, period_start, period_end)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_district_stats_district ON district_performance_stats(district_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_district_stats_period ON district_performance_stats(period_start, period_end);
|
|||
|
|
|
|||
|
|
-- Логины мессенджеров
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_messenger_logins (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
messenger messenger_type NOT NULL,
|
|||
|
|
login TEXT NOT NULL,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(employee_id, messenger)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_messenger_logins_employee ON employee_messenger_logins(employee_id);
|
|||
|
|
|
|||
|
|
-- HR данные: Паспортные данные
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_passport_data (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
series TEXT NOT NULL,
|
|||
|
|
number TEXT NOT NULL,
|
|||
|
|
issued_by TEXT NOT NULL,
|
|||
|
|
issued_date DATE NOT NULL,
|
|||
|
|
registration_address TEXT NOT NULL,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(employee_id)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_passport_data_employee ON employee_passport_data(employee_id);
|
|||
|
|
|
|||
|
|
-- HR данные: Трудовая книжка
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_labor_books (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
number TEXT NOT NULL,
|
|||
|
|
series TEXT,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(employee_id)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_labor_books_employee ON employee_labor_books(employee_id);
|
|||
|
|
|
|||
|
|
-- Записи в трудовой книжке
|
|||
|
|
CREATE TABLE IF NOT EXISTS labor_book_entries (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
labor_book_id BIGINT NOT NULL REFERENCES employee_labor_books(id) ON DELETE CASCADE,
|
|||
|
|
date DATE NOT NULL,
|
|||
|
|
organization TEXT NOT NULL,
|
|||
|
|
position TEXT NOT NULL,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_labor_book_entries_labor_book ON labor_book_entries(labor_book_id);
|
|||
|
|
|
|||
|
|
-- Заказ справок
|
|||
|
|
CREATE TYPE certificate_status AS ENUM ('requested', 'issued', 'ready');
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_certificates (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
type TEXT NOT NULL, -- Тип справки (2-НДФЛ, справка с места работы и т.д.)
|
|||
|
|
requested_date DATE NOT NULL,
|
|||
|
|
issued_date DATE,
|
|||
|
|
status certificate_status NOT NULL DEFAULT 'requested',
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_certificates_employee ON employee_certificates(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_certificates_status ON employee_certificates(status);
|
|||
|
|
|
|||
|
|
-- Прочие документы
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_other_documents (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
type TEXT NOT NULL,
|
|||
|
|
date DATE NOT NULL,
|
|||
|
|
file_url TEXT,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_other_documents_employee ON employee_other_documents(employee_id);
|
|||
|
|
|
|||
|
|
-- Типовые документы HR (печать/шаблоны в разделе Кадры)
|
|||
|
|
CREATE TABLE IF NOT EXISTS hr_template_documents (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL,
|
|||
|
|
file_path TEXT NOT NULL,
|
|||
|
|
original_filename TEXT,
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_hr_template_documents_created ON hr_template_documents(created_at DESC);
|
|||
|
|
|
|||
|
|
-- Бухгалтерская информация
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_accounting_data (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
inn TEXT, -- ИНН
|
|||
|
|
snils TEXT, -- СНИЛС
|
|||
|
|
bank_name TEXT, -- Название банка
|
|||
|
|
bank_account TEXT, -- Расчетный счет
|
|||
|
|
correspondent_account TEXT, -- Корреспондентский счет
|
|||
|
|
bik TEXT, -- БИК
|
|||
|
|
tax_id TEXT, -- КПП (если есть)
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(employee_id)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_accounting_data_employee ON employee_accounting_data(employee_id);
|
|||
|
|
|
|||
|
|
-- Характеристики договора
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_contracts (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
contract_type TEXT NOT NULL, -- Тип договора (трудовой, ГПХ, срочный и т.д.)
|
|||
|
|
contract_number TEXT, -- Номер договора
|
|||
|
|
start_date DATE NOT NULL, -- Дата начала
|
|||
|
|
end_date DATE, -- Дата окончания (NULL для бессрочного)
|
|||
|
|
probation_period_days INTEGER, -- Испытательный срок в днях
|
|||
|
|
work_schedule TEXT, -- График работы (полный день, частичная занятость и т.д.)
|
|||
|
|
work_mode TEXT, -- Режим работы (офис, удаленно, гибрид)
|
|||
|
|
contract_terms TEXT, -- Дополнительные условия договора
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_contracts_employee ON employee_contracts(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_contracts_dates ON employee_contracts(start_date, end_date);
|
|||
|
|
|
|||
|
|
-- ========= HR: ОТПУСКА =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_vacations (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
start_date DATE NOT NULL,
|
|||
|
|
end_date DATE NOT NULL,
|
|||
|
|
days_count INTEGER NOT NULL, -- Количество дней отпуска
|
|||
|
|
vacation_type TEXT NOT NULL DEFAULT 'annual', -- Тип отпуска (annual - ежегодный, unpaid - без сохранения зарплаты, study - учебный и т.д.)
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'planned' CHECK (status IN ('planned', 'approved', 'active', 'completed', 'canceled', 'rejected')),
|
|||
|
|
approved_by TEXT, -- Кто утвердил
|
|||
|
|
approved_at TIMESTAMPTZ, -- Дата утверждения
|
|||
|
|
notes TEXT, -- Примечания
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
CHECK (end_date >= start_date)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_vacations_employee ON employee_vacations(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_vacations_dates ON employee_vacations(start_date, end_date);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_vacations_status ON employee_vacations(status);
|
|||
|
|
|
|||
|
|
-- ========= HR: БОЛЬНИЧНЫЕ =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_sick_leaves (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
start_date DATE NOT NULL,
|
|||
|
|
end_date DATE, -- Может быть NULL, если больничный еще не закрыт
|
|||
|
|
days_count INTEGER, -- Количество дней (рассчитывается автоматически)
|
|||
|
|
sick_leave_number TEXT, -- Номер больничного листа
|
|||
|
|
diagnosis TEXT, -- Диагноз (если указан)
|
|||
|
|
medical_institution TEXT, -- Медицинское учреждение
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'closed', 'canceled')),
|
|||
|
|
closed_at TIMESTAMPTZ, -- Дата закрытия больничного
|
|||
|
|
expected_return_date DATE, -- Предварительная дата выхода
|
|||
|
|
notes TEXT, -- Примечания
|
|||
|
|
file_url TEXT, -- Ссылка на отсканированный больничный лист
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_sick_leaves_employee ON employee_sick_leaves(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_sick_leaves_dates ON employee_sick_leaves(start_date, end_date);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_sick_leaves_status ON employee_sick_leaves(status);
|
|||
|
|
|
|||
|
|
-- ========= HR: УВОЛЬНЕНИЯ =========
|
|||
|
|
|
|||
|
|
CREATE TYPE termination_status AS ENUM ('initiated', 'in_progress', 'completed', 'canceled');
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_terminations (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
termination_date DATE NOT NULL, -- Дата увольнения
|
|||
|
|
reason TEXT NOT NULL, -- Причина увольнения
|
|||
|
|
initiated_by TEXT NOT NULL, -- Кто инициировал увольнение
|
|||
|
|
initiated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
status termination_status NOT NULL DEFAULT 'initiated',
|
|||
|
|
-- Договор на увольнение
|
|||
|
|
termination_contract_number TEXT, -- Номер договора на увольнение
|
|||
|
|
termination_contract_date DATE, -- Дата договора
|
|||
|
|
termination_contract_file_url TEXT, -- Файл договора
|
|||
|
|
-- Расчеты
|
|||
|
|
final_settlement_amount NUMERIC(10, 2), -- Сумма окончательного расчета
|
|||
|
|
unused_vacation_days INTEGER, -- Неиспользованные дни отпуска
|
|||
|
|
compensation_amount NUMERIC(10, 2), -- Компенсация за неиспользованный отпуск
|
|||
|
|
severance_pay NUMERIC(10, 2), -- Выходное пособие
|
|||
|
|
other_payments NUMERIC(10, 2), -- Прочие выплаты
|
|||
|
|
deductions NUMERIC(10, 2), -- Удержания
|
|||
|
|
settlement_document_number TEXT, -- Номер документа расчета
|
|||
|
|
settlement_document_date DATE, -- Дата документа расчета
|
|||
|
|
settlement_document_file_url TEXT, -- Файл документа расчета
|
|||
|
|
-- Дополнительная информация
|
|||
|
|
notes TEXT, -- Примечания
|
|||
|
|
completed_at TIMESTAMPTZ, -- Дата завершения процедуры увольнения
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_terminations_employee ON employee_terminations(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_terminations_date ON employee_terminations(termination_date);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_terminations_status ON employee_terminations(status);
|
|||
|
|
|
|||
|
|
-- ========= HR: ОТГУЛЫ И ПРОГУЛЫ =========
|
|||
|
|
|
|||
|
|
CREATE TABLE IF NOT EXISTS employee_absences (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
absence_type VARCHAR(20) NOT NULL CHECK (absence_type IN ('day_off', 'absence', 'late', 'early_leave')), -- Тип: отгул, прогул, опоздание, ранний уход
|
|||
|
|
start_date DATE NOT NULL,
|
|||
|
|
end_date DATE, -- Может быть NULL для однодневных отгулов
|
|||
|
|
start_time TIME, -- Время начала (для опозданий и ранних уходов)
|
|||
|
|
end_time TIME, -- Время окончания (для опозданий и ранних уходов)
|
|||
|
|
days_count DECIMAL(5, 2) NOT NULL DEFAULT 1.0, -- Количество дней (может быть дробным для части дня)
|
|||
|
|
reason TEXT, -- Причина отсутствия
|
|||
|
|
requires_approval BOOLEAN NOT NULL DEFAULT TRUE, -- Требуется ли согласование
|
|||
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'canceled')), -- Статус согласования
|
|||
|
|
approved_by TEXT, -- Кто утвердил (руководитель)
|
|||
|
|
approved_at TIMESTAMPTZ, -- Дата и время утверждения
|
|||
|
|
approved_signature TEXT, -- Подпись руководителя (текст или путь к файлу)
|
|||
|
|
rejected_by TEXT, -- Кто отклонил
|
|||
|
|
rejected_at TIMESTAMPTZ, -- Дата отклонения
|
|||
|
|
rejection_reason TEXT, -- Причина отклонения
|
|||
|
|
notes TEXT, -- Примечания
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
CHECK (end_date IS NULL OR end_date >= start_date)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_absences_employee ON employee_absences(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_absences_dates ON employee_absences(start_date, end_date);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_absences_type ON employee_absences(absence_type);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_absences_status ON employee_absences(status);
|
|||
|
|
|
|||
|
|
-- Обновляем таблицу отпусков для поддержки согласования
|
|||
|
|
ALTER TABLE employee_vacations
|
|||
|
|
ADD COLUMN IF NOT EXISTS requires_approval BOOLEAN NOT NULL DEFAULT TRUE,
|
|||
|
|
ADD COLUMN IF NOT EXISTS approved_signature TEXT,
|
|||
|
|
ADD COLUMN IF NOT EXISTS rejected_by TEXT,
|
|||
|
|
ADD COLUMN IF NOT EXISTS rejected_at TIMESTAMPTZ,
|
|||
|
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT;
|
|||
|
|
|
|||
|
|
-- Обновляем таблицу больничных для поддержки согласования (если требуется)
|
|||
|
|
ALTER TABLE employee_sick_leaves
|
|||
|
|
ADD COLUMN IF NOT EXISTS requires_approval BOOLEAN NOT NULL DEFAULT FALSE,
|
|||
|
|
ADD COLUMN IF NOT EXISTS approved_by TEXT,
|
|||
|
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ,
|
|||
|
|
ADD COLUMN IF NOT EXISTS approved_signature TEXT;
|
|||
|
|
|
|||
|
|
-- ========= ПРОФИЛИ ПОЛЬЗОВАТЕЛЕЙ ПОРТАЛА =========
|
|||
|
|
|
|||
|
|
-- Профили пользователей портала
|
|||
|
|
-- Важно: профиль не может быть без сотрудника (employee_id NOT NULL)
|
|||
|
|
-- Но сотрудник может быть без профиля (связь необязательная со стороны employees)
|
|||
|
|
CREATE TABLE IF NOT EXISTS portal_users (
|
|||
|
|
id BIGSERIAL PRIMARY KEY,
|
|||
|
|
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
|
|||
|
|
email TEXT,
|
|||
|
|
login TEXT UNIQUE,
|
|||
|
|
photo_url TEXT, -- Путь к загруженному фото
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
UNIQUE(employee_id) -- Один профиль на одного сотрудника
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_portal_users_employee ON portal_users(employee_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_portal_users_login ON portal_users(login);
|
|||
|
|
|
|||
|
|
-- Изменяем таблицу employees: photo_url теперь хранит путь к загруженному файлу
|
|||
|
|
-- (или может быть NULL, если фото берется из профиля пользователя)
|
|||
|
|
|
|||
|
|
-- ========= HR: ВАКАНСИИ =========
|
|||
|
|
|
|||
|
|
-- Создание типа vacancy_status (с проверкой существования)
|
|||
|
|
DO $$
|
|||
|
|
BEGIN
|
|||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'vacancy_status') THEN
|
|||
|
|
CREATE TYPE vacancy_status AS ENUM ('urgent', 'active', 'paused', 'closed');
|
|||
|
|
END IF;
|
|||
|
|
END$$;
|
|||
|
|
|
|||
|
|
-- Таблица вакансий
|
|||
|
|
CREATE TABLE IF NOT EXISTS vacancies (
|
|||
|
|
id VARCHAR(50) PRIMARY KEY,
|
|||
|
|
position TEXT NOT NULL, -- Название должности
|
|||
|
|
department TEXT NOT NULL, -- Отдел
|
|||
|
|
status vacancy_status NOT NULL DEFAULT 'active',
|
|||
|
|
salary TEXT, -- Вилка зарплаты (например, "55 000 - 65 000 ₽")
|
|||
|
|
description TEXT NOT NULL, -- Описание вакансии
|
|||
|
|
requirements TEXT, -- Требования к кандидату
|
|||
|
|
conditions TEXT, -- Условия работы
|
|||
|
|
responsibilities TEXT, -- Обязанности
|
|||
|
|
posted_date DATE NOT NULL DEFAULT CURRENT_DATE, -- Дата публикации
|
|||
|
|
closing_date DATE, -- Дата закрытия вакансии
|
|||
|
|
applicants_count INTEGER DEFAULT 0, -- Количество откликов
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_vacancies_status ON vacancies(status);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_vacancies_department ON vacancies(department);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_vacancies_posted_date ON vacancies(posted_date DESC);
|
|||
|
|
|
|||
|
|
-- ========= HR: КАНДИДАТЫ =========
|
|||
|
|
|
|||
|
|
-- Создание типа candidate_stage (с проверкой существования)
|
|||
|
|
DO $$
|
|||
|
|
BEGIN
|
|||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'candidate_stage') THEN
|
|||
|
|
CREATE TYPE candidate_stage AS ENUM ('new', 'interview', 'probation', 'hired', 'rejected');
|
|||
|
|
ELSE
|
|||
|
|
-- Добавляем 'probation' если его еще нет
|
|||
|
|
IF NOT EXISTS (
|
|||
|
|
SELECT 1 FROM pg_enum
|
|||
|
|
WHERE enumlabel = 'probation'
|
|||
|
|
AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'candidate_stage')
|
|||
|
|
) THEN
|
|||
|
|
ALTER TYPE candidate_stage ADD VALUE IF NOT EXISTS 'probation';
|
|||
|
|
END IF;
|
|||
|
|
-- Удаляем 'offer' если он есть (заменяем на 'probation')
|
|||
|
|
-- Примечание: удаление значений из ENUM в PostgreSQL невозможно напрямую,
|
|||
|
|
-- поэтому оставляем 'offer' для обратной совместимости, но не используем в новом коде
|
|||
|
|
END IF;
|
|||
|
|
END$$;
|
|||
|
|
|
|||
|
|
-- Таблица кандидатов
|
|||
|
|
CREATE TABLE IF NOT EXISTS candidates (
|
|||
|
|
id VARCHAR(50) PRIMARY KEY,
|
|||
|
|
name TEXT NOT NULL, -- ФИО кандидата
|
|||
|
|
position TEXT NOT NULL, -- Позиция, на которую претендует
|
|||
|
|
vacancy_id VARCHAR(50) REFERENCES vacancies(id) ON DELETE SET NULL, -- Связь с вакансией
|
|||
|
|
stage candidate_stage NOT NULL DEFAULT 'new',
|
|||
|
|
phone TEXT NOT NULL,
|
|||
|
|
email TEXT,
|
|||
|
|
resume_url TEXT, -- Ссылка на резюме
|
|||
|
|
cover_letter TEXT, -- Сопроводительное письмо
|
|||
|
|
interview_date TIMESTAMPTZ, -- Дата собеседования
|
|||
|
|
interview_notes TEXT, -- Заметки с собеседования
|
|||
|
|
offer_salary NUMERIC(10, 2), -- Предложенная зарплата
|
|||
|
|
offer_date DATE, -- Дата предложения
|
|||
|
|
hired_date DATE, -- Дата найма
|
|||
|
|
rejected_reason TEXT, -- Причина отказа
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_candidates_vacancy ON candidates(vacancy_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_candidates_stage ON candidates(stage);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_candidates_position ON candidates(position);
|
|||
|
|
|
|||
|
|
-- ========= HR: СОБЫТИЯ КАНДИДАТОВ =========
|
|||
|
|
|
|||
|
|
-- Создание типа candidate_event_type
|
|||
|
|
DO $$
|
|||
|
|
BEGIN
|
|||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'candidate_event_type') THEN
|
|||
|
|
CREATE TYPE candidate_event_type AS ENUM (
|
|||
|
|
'call', -- Созвон
|
|||
|
|
'interview_1', -- Первое собеседование
|
|||
|
|
'interview_2', -- Второе собеседование
|
|||
|
|
'interview_3', -- Третье собеседование
|
|||
|
|
'test_task', -- Тестовое задание
|
|||
|
|
'offer', -- Оффер
|
|||
|
|
'offer_accepted', -- Оффер принят
|
|||
|
|
'offer_rejected', -- Оффер отклонен
|
|||
|
|
'probation_start', -- Начало испытательного срока
|
|||
|
|
'hired', -- Трудоустроен
|
|||
|
|
'rejected', -- Отклонен
|
|||
|
|
'other' -- Другое
|
|||
|
|
);
|
|||
|
|
END IF;
|
|||
|
|
END$$;
|
|||
|
|
|
|||
|
|
-- Создание типа candidate_event_result
|
|||
|
|
DO $$
|
|||
|
|
BEGIN
|
|||
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'candidate_event_result') THEN
|
|||
|
|
CREATE TYPE candidate_event_result AS ENUM ('success', 'failed', 'pending', 'cancelled');
|
|||
|
|
END IF;
|
|||
|
|
END$$;
|
|||
|
|
|
|||
|
|
-- Таблица событий кандидата
|
|||
|
|
CREATE TABLE IF NOT EXISTS candidate_events (
|
|||
|
|
id VARCHAR(50) PRIMARY KEY,
|
|||
|
|
candidate_id VARCHAR(50) NOT NULL REFERENCES candidates(id) ON DELETE CASCADE,
|
|||
|
|
event_type candidate_event_type NOT NULL,
|
|||
|
|
event_date TIMESTAMPTZ NOT NULL,
|
|||
|
|
notes TEXT, -- Заметки о событии
|
|||
|
|
result candidate_event_result DEFAULT 'pending', -- Результат события
|
|||
|
|
interviewer TEXT, -- Кто проводил (для собеседований)
|
|||
|
|
location TEXT, -- Место проведения (офис, онлайн и т.д.)
|
|||
|
|
duration_minutes INTEGER, -- Длительность в минутах
|
|||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_candidate_events_candidate ON candidate_events(candidate_id);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_candidate_events_type ON candidate_events(event_type);
|
|||
|
|
CREATE INDEX IF NOT EXISTS idx_candidate_events_date ON candidate_events(event_date DESC);
|
|||
|
|
|
|||
|
|
|