Initial commit MKD fixes

This commit is contained in:
Arsen
2026-02-04 00:17:04 +05:00
commit de94ad707b
312 changed files with 138754 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
-- Миграция: source и поля для ручной карточки заявки (диспетчерская)
-- Выполнить: psql -d mkd_control_center -f migrations/add_applications_manual_fields.sql
DO $$
BEGIN
-- source: 'doma' | 'manual'
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'source') THEN
ALTER TABLE applications ADD COLUMN source VARCHAR(20) NOT NULL DEFAULT 'doma';
END IF;
-- Откуда поступила заявка (Звонок, Заявка с сайта и т.д.)
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'source_channel') THEN
ALTER TABLE applications ADD COLUMN source_channel TEXT;
END IF;
-- Заявка от жителя (true) / не от жителя (false)
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'is_from_resident') THEN
ALTER TABLE applications ADD COLUMN is_from_resident BOOLEAN DEFAULT true;
END IF;
-- Телефон, ФИО (для не от жителя)
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'contact_phone') THEN
ALTER TABLE applications ADD COLUMN contact_phone TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'contact_name') THEN
ALTER TABLE applications ADD COLUMN contact_name TEXT;
END IF;
-- Классификатор: место инцидента, тип работ, в чём проблема
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'place_incident') THEN
ALTER TABLE applications ADD COLUMN place_incident TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'work_type') THEN
ALTER TABLE applications ADD COLUMN work_type TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'problem_detail') THEN
ALTER TABLE applications ADD COLUMN problem_detail TEXT;
END IF;
-- Чекбоксы: аварийная, платная, гарантийная
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'is_emergency') THEN
ALTER TABLE applications ADD COLUMN is_emergency BOOLEAN DEFAULT false;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'is_paid') THEN
ALTER TABLE applications ADD COLUMN is_paid BOOLEAN DEFAULT false;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'is_warranty') THEN
ALTER TABLE applications ADD COLUMN is_warranty BOOLEAN DEFAULT false;
END IF;
-- Назначение: исполнитель, ответственный, наблюдатели
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'executor_name') THEN
ALTER TABLE applications ADD COLUMN executor_name TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'responsible_name') THEN
ALTER TABLE applications ADD COLUMN responsible_name TEXT;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'observers_text') THEN
ALTER TABLE applications ADD COLUMN observers_text TEXT;
END IF;
-- Отображать заявку в мобильном приложении жителя
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'applications' AND column_name = 'show_in_app') THEN
ALTER TABLE applications ADD COLUMN show_in_app BOOLEAN DEFAULT false;
END IF;
END $$;

View File

@@ -0,0 +1,17 @@
-- Миграция для добавления массива файлов закрывающих документов в payment_invoices
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'payment_invoices'
AND column_name = 'closing_docs_files'
) THEN
ALTER TABLE payment_invoices
ADD COLUMN closing_docs_files JSONB DEFAULT '[]';
COMMENT ON COLUMN payment_invoices.closing_docs_files IS 'Список файлов закрывающих документов: [{filename, url, size, mimetype, uploadedAt, storedFilename}]';
END IF;
END $$;

View File

@@ -0,0 +1,11 @@
-- Добавить назначение "мероприятие" и привязку к pr_events
-- 1. Расширить CHECK purpose_type: добавить 'event'
ALTER TABLE payment_invoices DROP CONSTRAINT IF EXISTS payment_invoices_purpose_type_check;
ALTER TABLE payment_invoices ADD CONSTRAINT payment_invoices_purpose_type_check
CHECK (purpose_type IN ('building', 'district', 'legal', 'office', 'hr', 'other', 'event'));
-- 2. Колонка привязки счёта к мероприятию
ALTER TABLE payment_invoices ADD COLUMN IF NOT EXISTS purpose_event_id BIGINT REFERENCES pr_events(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_payment_invoices_purpose_event ON payment_invoices(purpose_event_id) WHERE purpose_event_id IS NOT NULL;
COMMENT ON COLUMN payment_invoices.purpose_event_id IS 'ID мероприятия (pr_events), если purpose_type = event';

View File

@@ -0,0 +1,3 @@
-- Ожидаемая дата возврата из ремонта
ALTER TABLE office_repair_requests
ADD COLUMN IF NOT EXISTS expected_return_date DATE;

View File

@@ -0,0 +1,4 @@
-- Добавить поле для изображения в pr_scheduled_posts
ALTER TABLE pr_scheduled_posts ADD COLUMN IF NOT EXISTS image_url TEXT;
COMMENT ON COLUMN pr_scheduled_posts.image_url IS 'URL изображения для поста';

View File

@@ -0,0 +1,8 @@
-- Миграция: добавление поля inventory в таблицу districts
-- Склад участка хранится в JSONB поле для гибкости структуры
ALTER TABLE districts
ADD COLUMN IF NOT EXISTS inventory JSONB DEFAULT '[]'::jsonb;
-- Создаем индекс для быстрого поиска по inventory (опционально, если нужно)
-- CREATE INDEX IF NOT EXISTS idx_districts_inventory ON districts USING GIN (inventory);

View File

@@ -0,0 +1,62 @@
-- Миграция для добавления полей item_type, service_items, material_items в таблицу payment_invoices
-- (если таблица уже существует без этих полей)
-- Проверяем и добавляем item_type
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'payment_invoices'
AND column_name = 'item_type'
) THEN
ALTER TABLE payment_invoices
ADD COLUMN item_type VARCHAR(20) NOT NULL DEFAULT 'service'
CHECK (item_type IN ('service', 'materials'));
COMMENT ON COLUMN payment_invoices.item_type IS 'Тип предмета счета: service (услуга) или materials (ТМЦ)';
END IF;
END $$;
-- Проверяем и добавляем service_items
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'payment_invoices'
AND column_name = 'service_items'
) THEN
ALTER TABLE payment_invoices
ADD COLUMN service_items JSONB DEFAULT '[]';
COMMENT ON COLUMN payment_invoices.service_items IS 'Список услуг: [{name, amount}]';
END IF;
END $$;
-- Проверяем и добавляем material_items
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'payment_invoices'
AND column_name = 'material_items'
) THEN
ALTER TABLE payment_invoices
ADD COLUMN material_items JSONB DEFAULT '[]';
COMMENT ON COLUMN payment_invoices.material_items IS 'Список ТМЦ: [{name, quantity, unit, pricePerUnit, amount}]';
END IF;
END $$;
-- Делаем service_description необязательным (для обратной совместимости)
DO $$
BEGIN
ALTER TABLE payment_invoices
ALTER COLUMN service_description DROP NOT NULL;
EXCEPTION
WHEN OTHERS THEN
-- Игнорируем ошибку, если колонка уже не NOT NULL
NULL;
END $$;

View File

@@ -0,0 +1,7 @@
-- Дополнительные поля журнала отключений (категория, тип работ, что сказать жителю и т.д.)
ALTER TABLE outages ADD COLUMN IF NOT EXISTS author_name VARCHAR(255);
ALTER TABLE outages ADD COLUMN IF NOT EXISTS category VARCHAR(100);
ALTER TABLE outages ADD COLUMN IF NOT EXISTS problem_detail VARCHAR(255);
ALTER TABLE outages ADD COLUMN IF NOT EXISTS work_type VARCHAR(50);
ALTER TABLE outages ADD COLUMN IF NOT EXISTS resident_message TEXT;
ALTER TABLE outages ADD COLUMN IF NOT EXISTS generate_news BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,9 @@
-- Номер платежки и признак оплаты наличными
ALTER TABLE payment_invoices ADD COLUMN IF NOT EXISTS payment_ref TEXT;
ALTER TABLE payment_invoices ADD COLUMN IF NOT EXISTS is_cash BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE payment_invoices ADD COLUMN IF NOT EXISTS postponed_date DATE;
ALTER TABLE payment_invoices ADD COLUMN IF NOT EXISTS cancel_reason TEXT;
COMMENT ON COLUMN payment_invoices.payment_ref IS 'Номер платежного поручения / платежки';
COMMENT ON COLUMN payment_invoices.is_cash IS 'Оплата наличными';
COMMENT ON COLUMN payment_invoices.postponed_date IS 'Дата переноса (при отложении)';
COMMENT ON COLUMN payment_invoices.cancel_reason IS 'Причина отмены';

View File

@@ -0,0 +1,6 @@
-- Связь счета с пунктом плана работ (план/факт)
ALTER TABLE payment_invoices ADD COLUMN IF NOT EXISTS plan_item_id TEXT;
ALTER TABLE payment_invoices ADD COLUMN IF NOT EXISTS plan_item_building_id TEXT;
COMMENT ON COLUMN payment_invoices.plan_item_id IS 'ID пункта плана работ (building.annualPlan[].id)';
COMMENT ON COLUMN payment_invoices.plan_item_building_id IS 'ID дома плана (building.id)';
CREATE INDEX IF NOT EXISTS idx_payment_invoices_plan_item ON payment_invoices(plan_item_building_id, plan_item_id) WHERE plan_item_id IS NOT NULL;

View File

@@ -0,0 +1,8 @@
-- Место проведения для мероприятий "для жителей": участок или дома отдельно
ALTER TABLE pr_events ADD COLUMN IF NOT EXISTS location_place_type VARCHAR(20) CHECK (location_place_type IN ('district', 'buildings'));
ALTER TABLE pr_events ADD COLUMN IF NOT EXISTS location_district_id VARCHAR(50) REFERENCES districts(id) ON DELETE SET NULL;
ALTER TABLE pr_events ADD COLUMN IF NOT EXISTS location_building_ids JSONB DEFAULT '[]';
COMMENT ON COLUMN pr_events.location_place_type IS 'Для жителей: district = участок, buildings = дома отдельно';
COMMENT ON COLUMN pr_events.location_district_id IS 'ID участка, если location_place_type = district';
COMMENT ON COLUMN pr_events.location_building_ids IS 'Массив ID домов, если location_place_type = buildings';

View File

@@ -0,0 +1,5 @@
-- Новые статусы заявок на ремонт техники
ALTER TYPE repair_request_status ADD VALUE IF NOT EXISTS 'search_contractor';
ALTER TYPE repair_request_status ADD VALUE IF NOT EXISTS 'waiting_delivery';
ALTER TYPE repair_request_status ADD VALUE IF NOT EXISTS 'taken_for_repair';
ALTER TYPE repair_request_status ADD VALUE IF NOT EXISTS 'self_repair';

View File

@@ -0,0 +1,9 @@
-- Новый статус: Договорились с подрядчиком
ALTER TYPE repair_request_status ADD VALUE IF NOT EXISTS 'agreed_with_contractor';
-- Поля для статусов: ожидание поставки, увезли на ремонт, договорились
ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS waiting_delivery_deadline TEXT;
ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS waiting_delivery_contacts TEXT;
ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS taken_for_repair_deadline TEXT;
ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS taken_for_repair_contacts TEXT;
ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS agreed_contractor_price NUMERIC(10, 2);

View File

@@ -0,0 +1,12 @@
-- Миграция: добавление поля report_type в таблицу financial_reports
-- Типы отчетов: debtors (должники), balance_sheet (оборотная сальдовая ведомость), other (другие)
ALTER TABLE financial_reports
ADD COLUMN IF NOT EXISTS report_type VARCHAR(50) DEFAULT 'other'
CHECK (report_type IN ('debtors', 'balance_sheet', 'other'));
-- Создаем индекс для быстрого поиска по типу отчета
CREATE INDEX IF NOT EXISTS idx_financial_reports_type ON financial_reports(report_type);
-- Комментарий к колонке
COMMENT ON COLUMN financial_reports.report_type IS 'Тип финансового отчета: debtors (должники), balance_sheet (оборотная сальдовая ведомость), other (другие)';

View File

@@ -0,0 +1,7 @@
-- Расширение report_type: добавление balance_sheet_76 (ОСВ по счёту 76.06 — лицевые счета)
-- Имя ограничения в PostgreSQL при ADD COLUMN CHECK часто: financial_reports_report_type_check
ALTER TABLE financial_reports DROP CONSTRAINT IF EXISTS financial_reports_report_type_check;
ALTER TABLE financial_reports ADD CONSTRAINT financial_reports_report_type_check
CHECK (report_type IN ('debtors', 'balance_sheet', 'balance_sheet_76', 'other'));
COMMENT ON COLUMN financial_reports.report_type IS 'Тип отчета: debtors, balance_sheet (ОСВ 20), balance_sheet_76 (ОСВ 76 — лицевые счета), other';

View File

@@ -0,0 +1,10 @@
-- Добавить scheduled_date в pr_post_topics (дата планируемой публикации)
ALTER TABLE pr_post_topics ADD COLUMN IF NOT EXISTS scheduled_date DATE;
-- Если scheduled_date пустое, заполнить из month (первый день месяца)
UPDATE pr_post_topics SET scheduled_date = (month || '-01')::date WHERE scheduled_date IS NULL;
-- Создать индекс
CREATE INDEX IF NOT EXISTS idx_pr_post_topics_scheduled_date ON pr_post_topics(scheduled_date);
COMMENT ON COLUMN pr_post_topics.scheduled_date IS 'Дата планируемой публикации поста по этой теме';

View File

@@ -0,0 +1,5 @@
-- Привязка фото отчёта (до/после) к задаче
ALTER TABLE work_photos
ADD COLUMN IF NOT EXISTS task_id VARCHAR(50);
CREATE INDEX IF NOT EXISTS idx_work_photos_task ON work_photos(task_id);

View File

@@ -0,0 +1,30 @@
-- Миграция: расширение профиля пользователя (план User Profile System)
-- Добавляет поля в portal_users и создает таблицу user_preferences
-- 1. Добавление полей в portal_users
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS given_name TEXT;
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS family_name TEXT;
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS birth_date DATE;
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS language VARCHAR(10) DEFAULT 'ru';
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS theme VARCHAR(20) DEFAULT 'light';
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS notification_email BOOLEAN DEFAULT true;
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS notification_push BOOLEAN DEFAULT true;
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT false;
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS last_login TIMESTAMPTZ;
ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS password_changed_at TIMESTAMPTZ;
-- 2. Создание таблицы user_preferences для расширяемых настроек
CREATE TABLE IF NOT EXISTS user_preferences (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES portal_users(id) ON DELETE CASCADE,
key TEXT NOT NULL,
value JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, key)
);
CREATE INDEX IF NOT EXISTS idx_user_preferences_user_id ON user_preferences(user_id);
COMMENT ON TABLE user_preferences IS 'Расширяемые настройки и предпочтения пользователя';

View File

@@ -0,0 +1,25 @@
-- Комментарии к заявкам (внутренние и с жителем)
CREATE TABLE IF NOT EXISTS application_comments (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
author_name TEXT NOT NULL,
type VARCHAR(20) NOT NULL DEFAULT 'internal',
text TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_application_comments_app ON application_comments(application_id);
CREATE INDEX IF NOT EXISTS idx_application_comments_type ON application_comments(type);
-- История изменений заявки
CREATE TABLE IF NOT EXISTS application_history (
id BIGSERIAL PRIMARY KEY,
application_id BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
changed_by TEXT NOT NULL,
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
field_name TEXT NOT NULL,
old_value TEXT,
new_value TEXT
);
CREATE INDEX IF NOT EXISTS idx_application_history_app ON application_history(application_id);

View File

@@ -0,0 +1,21 @@
-- Лицевые счета домов: отдельная таблица, чтобы не терять их при обновлении buildings.data
CREATE TABLE IF NOT EXISTS building_personal_accounts (
id VARCHAR(255) PRIMARY KEY,
building_id VARCHAR(255) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
data JSONB NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_building_personal_accounts_building_id ON building_personal_accounts(building_id);
COMMENT ON TABLE building_personal_accounts IS 'Лицевые счета (собственники, прописанные, приборы учёта) — вынесены из buildings.data для защиты от перезаписи';
-- Перенос существующих счетов из buildings.data->accounts в новую таблицу (идемпотентно: ON CONFLICT DO NOTHING)
INSERT INTO building_personal_accounts (id, building_id, data)
SELECT
COALESCE(acc->>'id', b.id || '-acc-migrated-' || t.ord),
b.id,
CASE WHEN (acc ? 'id') AND (acc->>'id' IS NOT NULL) AND (acc->>'id' <> '') THEN acc ELSE acc || jsonb_build_object('id', COALESCE(acc->>'id', b.id || '-acc-migrated-' || t.ord)) END
FROM buildings b,
LATERAL jsonb_array_elements(
CASE WHEN jsonb_typeof(COALESCE(b.data->'accounts', '[]'::jsonb)) = 'array' THEN COALESCE(b.data->'accounts', '[]'::jsonb) ELSE '[]'::jsonb END
) WITH ORDINALITY AS t(acc, ord)
ON CONFLICT (id) DO NOTHING;

View File

@@ -0,0 +1,19 @@
-- Новости компании: блок на сводке и реестр в офисе
CREATE TABLE IF NOT EXISTS company_news (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
body TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'pending', 'published')),
created_by BIGINT REFERENCES portal_users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
published_at TIMESTAMPTZ,
notify_departments JSONB DEFAULT '[]',
notify_employee_ids JSONB DEFAULT '[]'
);
CREATE INDEX IF NOT EXISTS idx_company_news_status ON company_news(status);
CREATE INDEX IF NOT EXISTS idx_company_news_created_at ON company_news(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_company_news_published_at ON company_news(published_at DESC NULLS LAST) WHERE status = 'published';
COMMENT ON TABLE company_news IS 'Новости компании: черновики и публикации с уведомлением по отделам/сотрудникам';

View File

@@ -0,0 +1,18 @@
-- Таблица строк отчёта по задолженности (один отчёт — много строк по лицевым счетам)
CREATE TABLE IF NOT EXISTS debtor_report_data (
id BIGSERIAL PRIMARY KEY,
report_id BIGINT NOT NULL REFERENCES financial_reports(id) ON DELETE CASCADE,
row_index INTEGER NOT NULL,
account TEXT NOT NULL,
responsible_name TEXT,
object_address TEXT,
months_debt INTEGER,
total_debt NUMERIC(14, 2) NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_debtor_report_data_report_id ON debtor_report_data(report_id);
CREATE INDEX IF NOT EXISTS idx_debtor_report_data_account ON debtor_report_data(account);
CREATE INDEX IF NOT EXISTS idx_debtor_report_data_total_debt ON debtor_report_data(total_debt);
COMMENT ON TABLE debtor_report_data IS 'Строки отчёта по задолженности: лицевой счёт, ФИО, адрес, месяцы долга, сумма';

View File

@@ -0,0 +1,18 @@
-- Связь сотрудник — участки (многие ко многим): один сотрудник может быть назначен на несколько участков
CREATE TABLE IF NOT EXISTS employee_districts (
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
district_id VARCHAR(50) NOT NULL REFERENCES districts(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (employee_id, district_id)
);
CREATE INDEX IF NOT EXISTS idx_employee_districts_employee ON employee_districts(employee_id);
CREATE INDEX IF NOT EXISTS idx_employee_districts_district ON employee_districts(district_id);
COMMENT ON TABLE employee_districts IS 'Назначения сотрудников на участки; один сотрудник может быть на нескольких участках';
-- Перенос существующих назначений из employees.assigned_district_id
INSERT INTO employee_districts (employee_id, district_id)
SELECT id, assigned_district_id
FROM employees
WHERE assigned_district_id IS NOT NULL AND assigned_district_id <> ''
ON CONFLICT (employee_id, district_id) DO NOTHING;

View File

@@ -0,0 +1,14 @@
-- Зоны ответственности: привязка сотрудников к подразделам модулей (уведомления, эффективность).
-- Запуск: psql -U user -d your_db -f create_employee_responsibility.sql
CREATE TABLE IF NOT EXISTS employee_responsibility (
id BIGSERIAL PRIMARY KEY,
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
section VARCHAR(50) NOT NULL,
sub_id VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(employee_id, section, sub_id)
);
CREATE INDEX IF NOT EXISTS idx_employee_responsibility_employee ON employee_responsibility(employee_id);
CREATE INDEX IF NOT EXISTS idx_employee_responsibility_zone ON employee_responsibility(section, sub_id);

View File

@@ -0,0 +1,42 @@
-- Справочник статей расходов
-- Категории расходов
CREATE TABLE IF NOT EXISTS expense_categories (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
code VARCHAR(50) UNIQUE, -- Код категории (опционально)
description TEXT,
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Статьи расходов внутри категорий
CREATE TABLE IF NOT EXISTS expense_items (
id BIGSERIAL PRIMARY KEY,
category_id BIGINT NOT NULL REFERENCES expense_categories(id) ON DELETE CASCADE,
name TEXT NOT NULL,
code VARCHAR(50), -- Код статьи (опционально)
description TEXT,
parent_item_id BIGINT REFERENCES expense_items(id) ON DELETE CASCADE, -- Для вложенных статей
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(category_id, name)
);
-- Индексы
CREATE INDEX IF NOT EXISTS idx_expense_items_category ON expense_items(category_id);
CREATE INDEX IF NOT EXISTS idx_expense_items_parent ON expense_items(parent_item_id);
CREATE INDEX IF NOT EXISTS idx_expense_items_active ON expense_items(is_active);
CREATE INDEX IF NOT EXISTS idx_expense_categories_active ON expense_categories(is_active);
-- Связь данных отчета со справочником
-- Добавляем поле для хранения ID статьи из справочника
ALTER TABLE building_financial_data
ADD COLUMN IF NOT EXISTS expense_item_mappings JSONB DEFAULT '{}';
COMMENT ON TABLE expense_categories IS 'Справочник категорий расходов';
COMMENT ON TABLE expense_items IS 'Справочник статей расходов внутри категорий';
COMMENT ON COLUMN building_financial_data.expense_item_mappings IS 'Маппинг расходов на статьи из справочника: { "item_id": amount, ... }';

View File

@@ -0,0 +1,16 @@
-- Счета: банки и наличка (кошельки) для отображения остатков в календаре
CREATE TABLE IF NOT EXISTS finance_accounts (
id BIGSERIAL PRIMARY KEY,
type VARCHAR(20) NOT NULL CHECK (type IN ('bank', 'cash')),
name TEXT NOT NULL,
balance NUMERIC(15, 2) NOT NULL DEFAULT 0,
currency VARCHAR(10) NOT NULL DEFAULT 'RUB',
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_finance_accounts_type ON finance_accounts(type);
CREATE INDEX IF NOT EXISTS idx_finance_accounts_active ON finance_accounts(is_active);
COMMENT ON TABLE finance_accounts IS 'Банковские счета и кошельки (наличка) для отображения остатков в календаре оплат';

View File

@@ -0,0 +1,21 @@
-- История перемещений и событий по оборудованию (закупка, выдача, перемещения, ремонты, списание)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'office_equipment_history_type') THEN
CREATE TYPE office_equipment_history_type AS ENUM ('purchase', 'issue', 'transfer', 'repair', 'write_off');
END IF;
END$$;
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,
assigned_from TEXT,
reason TEXT,
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);

View File

@@ -0,0 +1,16 @@
-- Журнал отключений (вода, свет и т.д.) по домам
CREATE TABLE IF NOT EXISTS outages (
id BIGSERIAL PRIMARY KEY,
building_id VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
start_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
end_at TIMESTAMPTZ,
type VARCHAR(50),
description TEXT,
active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_outages_building ON outages(building_id);
CREATE INDEX IF NOT EXISTS idx_outages_active ON outages(active);
CREATE INDEX IF NOT EXISTS idx_outages_start_at ON outages(start_at);

View File

@@ -0,0 +1,28 @@
-- Записи платежного календаря (расходы и поступления, по счету / без счета / наличка)
CREATE TABLE IF NOT EXISTS payment_calendar_entries (
id BIGSERIAL PRIMARY KEY,
direction VARCHAR(20) NOT NULL CHECK (direction IN ('outgoing', 'incoming')),
type VARCHAR(20) NOT NULL CHECK (type IN ('invoice', 'manual', 'cash')),
payment_invoice_id BIGINT REFERENCES payment_invoices(id) ON DELETE SET NULL,
category TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT '',
amount NUMERIC(15, 2) NOT NULL,
scheduled_date DATE NOT NULL,
payment_date DATE,
probability VARCHAR(20) NOT NULL DEFAULT 'confirmed' CHECK (probability IN ('confirmed', 'high', 'medium', 'low')),
currency VARCHAR(10) NOT NULL DEFAULT 'RUB',
is_cash BOOLEAN NOT NULL DEFAULT FALSE,
contractor_name TEXT NOT NULL DEFAULT '',
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_payment_calendar_entries_direction ON payment_calendar_entries(direction);
CREATE INDEX IF NOT EXISTS idx_payment_calendar_entries_type ON payment_calendar_entries(type);
CREATE INDEX IF NOT EXISTS idx_payment_calendar_entries_scheduled_date ON payment_calendar_entries(scheduled_date);
CREATE INDEX IF NOT EXISTS idx_payment_calendar_entries_payment_date ON payment_calendar_entries(payment_date);
CREATE INDEX IF NOT EXISTS idx_payment_calendar_entries_payment_invoice_id ON payment_calendar_entries(payment_invoice_id);
COMMENT ON TABLE payment_calendar_entries IS 'Записи платежного календаря: расходы и поступления (по счету, без счета, наличка)';

View File

@@ -0,0 +1,18 @@
-- Справочник статей доходов и расходов для платежного календаря
CREATE TABLE IF NOT EXISTS payment_categories (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
code VARCHAR(50),
direction VARCHAR(20) NOT NULL CHECK (direction IN ('income', 'expense')),
parent_id BIGINT REFERENCES payment_categories(id) ON DELETE SET NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
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_payment_categories_direction ON payment_categories(direction);
CREATE INDEX IF NOT EXISTS idx_payment_categories_parent ON payment_categories(parent_id);
CREATE INDEX IF NOT EXISTS idx_payment_categories_active ON payment_categories(is_active);
COMMENT ON TABLE payment_categories IS 'Справочник статей доходов и расходов для платежного календаря';

View File

@@ -0,0 +1,82 @@
-- Миграция для создания таблицы счетов на оплату
CREATE TABLE IF NOT EXISTS payment_invoices (
id BIGSERIAL PRIMARY KEY,
invoice_number TEXT UNIQUE NOT NULL,
created_by TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Назначение счета
purpose_type VARCHAR(50) NOT NULL CHECK (purpose_type IN ('building', 'district', 'legal', 'office', 'hr', 'other')),
purpose_building_ids JSONB DEFAULT '[]', -- массив ID домов
purpose_district_ids JSONB DEFAULT '[]', -- массив ID участков
purpose_description TEXT, -- описание для 'other'
-- Формат оплаты
payment_format VARCHAR(20) NOT NULL CHECK (payment_format IN ('prepayment', 'postpayment', 'advance')),
-- Тип предмета счета (услуга или ТМЦ)
item_type VARCHAR(20) NOT NULL DEFAULT 'service' CHECK (item_type IN ('service', 'materials')),
-- Информация о подрядчике
contractor_name TEXT NOT NULL,
contractor_inn TEXT,
-- Описание услуги или ТМЦ (для обратной совместимости)
service_description TEXT,
-- Список услуг (если item_type = 'service')
service_items JSONB DEFAULT '[]',
-- Список ТМЦ (если item_type = 'materials')
material_items JSONB DEFAULT '[]',
total_amount NUMERIC(15, 2) NOT NULL,
-- Распределение суммы
distribution_method VARCHAR(20) CHECK (distribution_method IN ('equal', 'by_area', 'manual')),
distribution_data JSONB DEFAULT '{}', -- детали распределения по домам/участкам
-- Workflow статусы
status VARCHAR(30) NOT NULL DEFAULT 'draft' CHECK (status IN (
'draft',
'pending_manager_approval',
'pending_finance_manager_approval',
'approved',
'scheduled',
'paid',
'postponed',
'cancelled',
'rejected',
'completed'
)),
current_approver_role VARCHAR(50), -- текущий этап согласования
approval_history JSONB DEFAULT '[]', -- история согласований
-- Отклонение
rejection_reason TEXT,
-- Даты
scheduled_date DATE, -- дата в графике платежей
payment_date DATE, -- фактическая дата оплаты
-- Статусы выполнения
is_completed BOOLEAN DEFAULT FALSE, -- для постоплаты - выполнено ли
closing_docs_received BOOLEAN DEFAULT FALSE, -- закрывающие документы получены
-- Дополнительно
notes TEXT,
file_urls JSONB DEFAULT '[]' -- массив путей к файлам счета
);
-- Индексы для быстрого поиска
CREATE INDEX IF NOT EXISTS idx_payment_invoices_status ON payment_invoices(status);
CREATE INDEX IF NOT EXISTS idx_payment_invoices_created_by ON payment_invoices(created_by);
CREATE INDEX IF NOT EXISTS idx_payment_invoices_purpose_type ON payment_invoices(purpose_type);
CREATE INDEX IF NOT EXISTS idx_payment_invoices_payment_format ON payment_invoices(payment_format);
CREATE INDEX IF NOT EXISTS idx_payment_invoices_scheduled_date ON payment_invoices(scheduled_date);
CREATE INDEX IF NOT EXISTS idx_payment_invoices_invoice_number ON payment_invoices(invoice_number);
-- Комментарии к таблице и колонкам
COMMENT ON TABLE payment_invoices IS 'Счета на оплату с workflow согласования';
COMMENT ON COLUMN payment_invoices.purpose_type IS 'Назначение: building, district, legal, office, hr, other';
COMMENT ON COLUMN payment_invoices.payment_format IS 'Формат оплаты: prepayment, postpayment, advance';
COMMENT ON COLUMN payment_invoices.distribution_method IS 'Метод распределения: equal, by_area, manual';
COMMENT ON COLUMN payment_invoices.approval_history IS 'История согласований: [{role, userId, action, date, comment}]';

View File

@@ -0,0 +1,12 @@
-- Справочник должностей (для панели управления и выбора при создании сотрудника)
CREATE TABLE IF NOT EXISTS positions (
id VARCHAR(50) PRIMARY KEY,
name TEXT NOT NULL,
is_managerial BOOLEAN NOT NULL DEFAULT FALSE,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_positions_sort ON positions(sort_order);
COMMENT ON TABLE positions IS 'Справочник должностей; is_managerial — руководящая должность';

View File

@@ -0,0 +1,19 @@
-- PR Привлечение: действия, которыми привлекали людей (рассылка, мероприятие, пост и т.д.)
CREATE TABLE IF NOT EXISTS pr_attraction_actions (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
channel_id BIGINT REFERENCES pr_smm_channels(id) ON DELETE SET NULL,
action_type VARCHAR(20) NOT NULL CHECK (action_type IN ('mailing', 'event', 'post', 'other')),
action_date DATE NOT NULL DEFAULT CURRENT_DATE,
new_subscribers_attributed INT,
event_id BIGINT REFERENCES pr_events(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT
);
CREATE INDEX IF NOT EXISTS idx_pr_attraction_actions_channel ON pr_attraction_actions(channel_id);
CREATE INDEX IF NOT EXISTS idx_pr_attraction_actions_type ON pr_attraction_actions(action_type);
CREATE INDEX IF NOT EXISTS idx_pr_attraction_actions_date ON pr_attraction_actions(action_date DESC);
COMMENT ON TABLE pr_attraction_actions IS 'Действия привлечения: рассылка, мероприятие, пост и т.д., с приростом подписчиков';

View File

@@ -0,0 +1,52 @@
-- PR Мероприятия: pr_events, pr_event_assignees, pr_event_photos
-- Таблица мероприятий
CREATE TABLE IF NOT EXISTS pr_events (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
date DATE NOT NULL,
type VARCHAR(20) NOT NULL CHECK (type IN ('resident', 'internal')),
category VARCHAR(20) NOT NULL CHECK (category IN ('holiday', 'eco', 'sport', 'training', 'meeting')),
status VARCHAR(20) NOT NULL DEFAULT 'planned' CHECK (status IN ('planned', 'in_progress', 'completed', 'canceled')),
location TEXT,
location_type VARCHAR(20) CHECK (location_type IN ('building', 'office')),
location_building_id VARCHAR(50) REFERENCES buildings(id) ON DELETE SET NULL,
attendees_count INTEGER NOT NULL DEFAULT 0,
budget NUMERIC(15, 2),
short_plan TEXT,
announcement TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT
);
CREATE INDEX IF NOT EXISTS idx_pr_events_date ON pr_events(date DESC);
CREATE INDEX IF NOT EXISTS idx_pr_events_status ON pr_events(status);
CREATE INDEX IF NOT EXISTS idx_pr_events_type ON pr_events(type);
CREATE INDEX IF NOT EXISTS idx_pr_events_location_building ON pr_events(location_building_id) WHERE location_building_id IS NOT NULL;
-- Сотрудники, назначенные на помощь по мероприятию
CREATE TABLE IF NOT EXISTS pr_event_assignees (
event_id BIGINT NOT NULL REFERENCES pr_events(id) ON DELETE CASCADE,
employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
PRIMARY KEY (event_id, employee_id)
);
CREATE INDEX IF NOT EXISTS idx_pr_event_assignees_event ON pr_event_assignees(event_id);
CREATE INDEX IF NOT EXISTS idx_pr_event_assignees_employee ON pr_event_assignees(employee_id);
-- Фотоотчёт по мероприятию (привязка к дому/офису для внутренних)
CREATE TABLE IF NOT EXISTS pr_event_photos (
id BIGSERIAL PRIMARY KEY,
event_id BIGINT NOT NULL REFERENCES pr_events(id) ON DELETE CASCADE,
photo_url TEXT NOT NULL,
caption TEXT,
location_type VARCHAR(20) CHECK (location_type IN ('building', 'office')),
location_building_id VARCHAR(50) REFERENCES buildings(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_pr_event_photos_event ON pr_event_photos(event_id);
COMMENT ON TABLE pr_events IS 'Мероприятия PR: для жителей и внутренние';
COMMENT ON TABLE pr_event_assignees IS 'Сотрудники для помощи по мероприятию';
COMMENT ON TABLE pr_event_photos IS 'Фотоотчёт по мероприятию';

View File

@@ -0,0 +1,21 @@
-- PR График публикации (календарь тем для постов)
CREATE TABLE IF NOT EXISTS pr_post_topics (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
scheduled_date DATE, -- дата планируемой публикации (может быть NULL для старых записей)
month VARCHAR(7) NOT NULL, -- формат YYYY-MM для фильтрации
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'pending_approval', 'approved', 'rejected')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
approved_at TIMESTAMPTZ,
approved_by TEXT,
rejection_reason TEXT,
created_at_month VARCHAR(7) GENERATED ALWAYS AS (TO_CHAR(created_at, 'YYYY-MM')) STORED
);
CREATE INDEX IF NOT EXISTS idx_pr_post_topics_month ON pr_post_topics(month DESC);
CREATE INDEX IF NOT EXISTS idx_pr_post_topics_scheduled_date ON pr_post_topics(scheduled_date);
CREATE INDEX IF NOT EXISTS idx_pr_post_topics_status ON pr_post_topics(status);
COMMENT ON TABLE pr_post_topics IS 'График публикации (календарь тем) - план публикаций на месяц. По этим темам создаются посты с контентом.';

View File

@@ -0,0 +1,23 @@
-- PR Отложенные посты (для одобрения перед публикацией)
CREATE TABLE IF NOT EXISTS pr_scheduled_posts (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
channel_ids JSONB DEFAULT '[]', -- массив ID каналов из pr_smm_channels
scheduled_at TIMESTAMPTZ NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'pending_approval', 'approved', 'rejected', 'edited', 'published')),
topic_id BIGINT REFERENCES pr_post_topics(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
approved_at TIMESTAMPTZ,
approved_by TEXT,
rejection_reason TEXT,
edited_content TEXT, -- если статус edited, здесь новая версия
published_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_pr_scheduled_posts_scheduled ON pr_scheduled_posts(scheduled_at DESC);
CREATE INDEX IF NOT EXISTS idx_pr_scheduled_posts_status ON pr_scheduled_posts(status);
CREATE INDEX IF NOT EXISTS idx_pr_scheduled_posts_topic ON pr_scheduled_posts(topic_id);
COMMENT ON TABLE pr_scheduled_posts IS 'Отложенные посты для одобрения перед публикацией';

View File

@@ -0,0 +1,28 @@
-- PR SMM: каналы и снимки подписчиков
-- Каналы (Telegram, VK, WhatsApp и др.)
CREATE TABLE IF NOT EXISTS pr_smm_channels (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
type VARCHAR(20) NOT NULL CHECK (type IN ('tg', 'vk', 'wa', 'other')),
url TEXT,
sort_order INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_pr_smm_channels_sort ON pr_smm_channels(sort_order);
-- Снимки количества подписчиков по дате (история фиксаций)
CREATE TABLE IF NOT EXISTS pr_smm_subscriber_snapshots (
id BIGSERIAL PRIMARY KEY,
channel_id BIGINT NOT NULL REFERENCES pr_smm_channels(id) ON DELETE CASCADE,
subscribers_count INT NOT NULL,
recorded_at DATE NOT NULL DEFAULT CURRENT_DATE,
note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_pr_smm_snapshots_channel ON pr_smm_subscriber_snapshots(channel_id);
CREATE INDEX IF NOT EXISTS idx_pr_smm_snapshots_recorded ON pr_smm_subscriber_snapshots(recorded_at DESC);
COMMENT ON TABLE pr_smm_channels IS 'SMM-каналы УК: Telegram, VK, WhatsApp и др.';
COMMENT ON TABLE pr_smm_subscriber_snapshots IS 'Фиксации количества подписчиков по дате для истории';

View File

@@ -0,0 +1,33 @@
-- Таблица строк ОСВ по счёту 76.06 (лицевые счета жителей)
CREATE TABLE IF NOT EXISTS report_76_rows (
id BIGSERIAL PRIMARY KEY,
report_id BIGINT NOT NULL REFERENCES financial_reports(id) ON DELETE CASCADE,
row_index INTEGER NOT NULL,
account_label TEXT NOT NULL,
account_ls TEXT,
saldo_start_debet NUMERIC(15, 2) DEFAULT 0,
turnover_debet NUMERIC(15, 2) DEFAULT 0,
turnover_credit NUMERIC(15, 2) DEFAULT 0,
saldo_end_debet NUMERIC(15, 2) DEFAULT 0,
saldo_end_credit NUMERIC(15, 2) DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_report_76_rows_report_id ON report_76_rows(report_id);
COMMENT ON TABLE report_76_rows IS 'Строки ОСВ по счёту 76.06 — лицевые счета (жители) из загруженного отчёта';
-- Таблица сопоставления домов и лицевых счетов (для фильтра ОСВ 76 по дому и отображения в карточке дома)
CREATE TABLE IF NOT EXISTS building_personal_account_mappings (
id BIGSERIAL PRIMARY KEY,
building_id VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
account_ls TEXT NOT NULL,
account_label TEXT,
apartment TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(building_id, account_ls)
);
CREATE INDEX IF NOT EXISTS idx_building_personal_account_mappings_building ON building_personal_account_mappings(building_id);
CREATE INDEX IF NOT EXISTS idx_building_personal_account_mappings_account_ls ON building_personal_account_mappings(account_ls);
COMMENT ON TABLE building_personal_account_mappings IS 'Сопоставление домов и лицевых счетов (для ОСВ 76): фильтр по дому, отображение в карточке дома';

View File

@@ -0,0 +1,24 @@
-- Миграция для создания таблицы ролей пользователей
CREATE TABLE IF NOT EXISTS user_roles (
id BIGSERIAL PRIMARY KEY,
user_id TEXT NOT NULL,
role VARCHAR(50) NOT NULL CHECK (role IN (
'manager',
'finance_manager',
'financier',
'finance_director',
'director',
'top_management'
)),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, role)
);
-- Индексы
CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
CREATE INDEX IF NOT EXISTS idx_user_roles_role ON user_roles(role);
-- Комментарии
COMMENT ON TABLE user_roles IS 'Роли пользователей для workflow согласования счетов';
COMMENT ON COLUMN user_roles.role IS 'Роль: manager, finance_manager, financier, finance_director, director, top_management';

View File

@@ -0,0 +1,27 @@
-- Аудиты: статусы, индекс сложности, данные осмотра по пунктам/подпунктам
-- Статус аудита: новый, в работе, завершён
ALTER TABLE development_audits ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'new';
UPDATE development_audits SET status = 'completed' WHERE status IS NULL OR status = '';
ALTER TABLE development_audits ALTER COLUMN status SET DEFAULT 'new';
ALTER TABLE development_audits ALTER COLUMN status SET NOT NULL;
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'development_audits_status_check') THEN
ALTER TABLE development_audits ADD CONSTRAINT development_audits_status_check
CHECK (status IN ('new', 'in_progress', 'completed'));
END IF;
END $$;
-- Индекс сложности дома (0100)
ALTER TABLE development_audits ADD COLUMN IF NOT EXISTS complexity_index NUMERIC(5, 2);
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'development_audits_complexity_index_check') THEN
ALTER TABLE development_audits ADD CONSTRAINT development_audits_complexity_index_check
CHECK (complexity_index IS NULL OR (complexity_index >= 0 AND complexity_index <= 100));
END IF;
END $$;
-- Данные осмотра: пункты и подпункты (JSONB)
ALTER TABLE development_audits ADD COLUMN IF NOT EXISTS inspection_data JSONB DEFAULT '{}';
CREATE INDEX IF NOT EXISTS idx_dev_audits_status ON development_audits(status);

View File

@@ -0,0 +1,7 @@
-- ОСС: повестка (пункты) и голоса по каждому пункту в реестре
-- Повестка собрания: массив формулировок пунктов (JSON array of strings)
ALTER TABLE development_oss_sessions ADD COLUMN IF NOT EXISTS agenda_items JSONB NOT NULL DEFAULT '[]';
-- Голоса по пунктам в реестре: объект { "0": "for", "1": "against", "2": "abstain" } (индекс пункта -> for/against/abstain)
ALTER TABLE development_oss_registry ADD COLUMN IF NOT EXISTS votes_by_item JSONB NOT NULL DEFAULT '{}';

View File

@@ -0,0 +1,17 @@
-- Добавляем этап «Анализ» (analysis) в воронку развития (после Входящие)
ALTER TABLE development_pipeline DROP CONSTRAINT IF EXISTS development_pipeline_status_check;
ALTER TABLE development_pipeline ADD CONSTRAINT development_pipeline_status_check
CHECK (status IN (
'incoming',
'analysis',
'agenda_approval',
'in_person',
'absentee',
'protocol_formation',
'protocol_to_gzhi',
'gzhi_order',
'success',
'failure'
));

View File

@@ -0,0 +1,31 @@
-- Воронка развития: новые этапы (9 пунктов)
-- 1) Входящие 2) Согласование повестки 3) Очная часть 4) Заочная часть
-- 5) Формирование протокола 6) Отправка протокола в ГЖИ 7) Приказ ГЖИ 8) Успех 9) Провал
-- Удаляем старый CHECK по status (автоимя в PG обычно development_pipeline_status_check)
ALTER TABLE development_pipeline DROP CONSTRAINT IF EXISTS development_pipeline_status_check;
-- Миграция старых статусов в новые
UPDATE development_pipeline SET status = 'incoming' WHERE status = 'analysis';
UPDATE development_pipeline SET status = 'agenda_approval' WHERE status = 'negotiation';
UPDATE development_pipeline SET status = 'in_person' WHERE status = 'preparation';
UPDATE development_pipeline SET status = 'absentee' WHERE status = 'voting';
UPDATE development_pipeline SET status = 'success' WHERE status = 'transfer';
-- Добавляем новый CHECK (включая этап «Анализ»)
ALTER TABLE development_pipeline ADD CONSTRAINT development_pipeline_status_check
CHECK (status IN (
'incoming',
'analysis',
'agenda_approval',
'in_person',
'absentee',
'protocol_formation',
'protocol_to_gzhi',
'gzhi_order',
'success',
'failure'
));
-- Дефолт для новых записей (на случай если где-то вставляют без status)
-- ALTER TABLE development_pipeline ALTER COLUMN status SET DEFAULT 'incoming';

View File

@@ -0,0 +1,15 @@
-- Обновление UNIQUE constraint для building_financial_data
-- Разрешаем накопление данных по периодам из разных отчетов
-- Удаляем старый constraint
ALTER TABLE building_financial_data
DROP CONSTRAINT IF EXISTS building_financial_data_building_id_period_start_period_end_period_type_key;
-- Создаем новый constraint, который включает report_id
-- Это позволяет иметь несколько записей для одного периода, но из разных отчетов
ALTER TABLE building_financial_data
ADD CONSTRAINT building_financial_data_unique
UNIQUE (building_id, report_id, period_start, period_end, period_type);
COMMENT ON CONSTRAINT building_financial_data_unique ON building_financial_data IS
'Уникальность по дому, отчету и периоду. Позволяет накапливать данные по периодам из разных отчетов.';