Initial commit
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
-- AegisOne Engineering — PostgreSQL Schema
|
||||
-- Adapted from MySQL for Python/FastAPI migration
|
||||
|
||||
-- Users
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
login VARCHAR(50) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(20) NOT NULL DEFAULT 'technician' CHECK (role IN ('owner','engineer','technician')),
|
||||
full_name VARCHAR(255) NOT NULL,
|
||||
phone VARCHAR(50) DEFAULT '',
|
||||
email VARCHAR(255) DEFAULT '',
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_role ON users(role);
|
||||
CREATE INDEX idx_users_active ON users(is_active);
|
||||
|
||||
-- Login attempts
|
||||
CREATE TABLE IF NOT EXISTS login_attempts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ip_address VARCHAR(45) NOT NULL,
|
||||
login VARCHAR(50) NOT NULL DEFAULT '',
|
||||
attempt_time TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_login_attempts_ip ON login_attempts(ip_address);
|
||||
CREATE INDEX idx_login_attempts_time ON login_attempts(attempt_time);
|
||||
|
||||
-- Audit log
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
action VARCHAR(255) NOT NULL,
|
||||
details TEXT,
|
||||
ip_address VARCHAR(45) DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_audit_user ON audit_log(user_id);
|
||||
CREATE INDEX idx_audit_action ON audit_log(action);
|
||||
CREATE INDEX idx_audit_created ON audit_log(created_at);
|
||||
|
||||
-- Customers
|
||||
CREATE TABLE IF NOT EXISTS customers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
inn VARCHAR(12) DEFAULT '',
|
||||
kpp VARCHAR(9) DEFAULT '',
|
||||
legal_address VARCHAR(500) DEFAULT '',
|
||||
contact_person VARCHAR(255) DEFAULT '',
|
||||
contact_phone VARCHAR(50) DEFAULT '',
|
||||
contact_email VARCHAR(255) DEFAULT '',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','inactive','prospect')),
|
||||
notes TEXT,
|
||||
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_customers_status ON customers(status);
|
||||
CREATE INDEX idx_customers_inn ON customers(inn);
|
||||
|
||||
-- Objects
|
||||
CREATE TABLE IF NOT EXISTS objects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address VARCHAR(500) DEFAULT '',
|
||||
object_type VARCHAR(100) DEFAULT '',
|
||||
area_sqm NUMERIC(10,2) DEFAULT 0,
|
||||
employees_count INTEGER DEFAULT 0,
|
||||
contact_person VARCHAR(255) DEFAULT '',
|
||||
contact_phone VARCHAR(50) DEFAULT '',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','inactive','audit','prospective')),
|
||||
notes TEXT,
|
||||
risk_score NUMERIC(5,2) DEFAULT 0,
|
||||
complexity_index NUMERIC(5,2) DEFAULT 0,
|
||||
infrastructure_load NUMERIC(5,2) DEFAULT 0,
|
||||
service_history NUMERIC(5,2) DEFAULT 0,
|
||||
object_index NUMERIC(5,2) DEFAULT 0,
|
||||
sla_price_monthly NUMERIC(12,2) DEFAULT 0,
|
||||
region_factor NUMERIC(3,2) DEFAULT 1.00,
|
||||
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_objects_status ON objects(status);
|
||||
CREATE INDEX idx_objects_customer ON objects(customer_id);
|
||||
|
||||
-- Object assignments
|
||||
CREATE TABLE IF NOT EXISTS object_assignments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role VARCHAR(20) NOT NULL CHECK (role IN ('engineer','technician')),
|
||||
assigned_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
unassigned_at TIMESTAMP DEFAULT NULL
|
||||
);
|
||||
CREATE INDEX idx_assignments_object ON object_assignments(object_id);
|
||||
CREATE INDEX idx_assignments_user ON object_assignments(user_id);
|
||||
|
||||
-- Tasks
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
|
||||
assigned_to INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
priority VARCHAR(4) NOT NULL DEFAULT 'P3' CHECK (priority IN ('P1','P2','P3','P4')),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open','in_progress','completed','cancelled')),
|
||||
deadline TIMESTAMP,
|
||||
sla_remaining_hours NUMERIC(6,1) DEFAULT 0,
|
||||
response_time_minutes INTEGER DEFAULT 0,
|
||||
resolution_time_minutes INTEGER DEFAULT 0,
|
||||
sla_compliant BOOLEAN,
|
||||
checklist_data JSONB,
|
||||
result_notes TEXT,
|
||||
closed_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_tasks_object ON tasks(object_id);
|
||||
CREATE INDEX idx_tasks_assigned ON tasks(assigned_to);
|
||||
CREATE INDEX idx_tasks_status ON tasks(status);
|
||||
CREATE INDEX idx_tasks_priority ON tasks(priority);
|
||||
|
||||
-- Task comments
|
||||
CREATE TABLE IF NOT EXISTS task_comments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
comment TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_comments_task ON task_comments(task_id);
|
||||
|
||||
-- Task photos
|
||||
CREATE TABLE IF NOT EXISTS task_photos (
|
||||
id SERIAL PRIMARY KEY,
|
||||
task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL,
|
||||
report_id INTEGER REFERENCES reports(id) ON DELETE SET NULL,
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
description VARCHAR(500) DEFAULT '',
|
||||
type VARCHAR(20) DEFAULT 'other' CHECK (type IN ('before','after','evidence','other')),
|
||||
uploaded_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_photos_task ON task_photos(task_id);
|
||||
|
||||
-- Reports (created after task_photos for FK reference)
|
||||
CREATE TABLE IF NOT EXISTS reports (
|
||||
id SERIAL PRIMARY KEY,
|
||||
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
|
||||
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
report_type VARCHAR(20) NOT NULL DEFAULT 'service' CHECK (report_type IN ('inspection','service','emergency','audit','monthly')),
|
||||
title VARCHAR(500) DEFAULT '',
|
||||
description TEXT,
|
||||
findings TEXT,
|
||||
recommendations TEXT,
|
||||
work_done TEXT,
|
||||
cause_classification VARCHAR(100),
|
||||
result_status VARCHAR(20) DEFAULT 'ok' CHECK (result_status IN ('fixed','partial','revisit','ok')),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','final','cancelled')),
|
||||
client_confirmed BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_reports_object ON reports(object_id);
|
||||
CREATE INDEX idx_reports_author ON reports(created_by);
|
||||
|
||||
-- Add FK for task_photos.report_id after reports table exists
|
||||
-- (handled in application-level migration)
|
||||
|
||||
-- Questionnaire sessions
|
||||
CREATE TABLE IF NOT EXISTS questionnaire_sessions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
object_id INTEGER REFERENCES objects(id) ON DELETE SET NULL,
|
||||
client_name VARCHAR(255) DEFAULT '',
|
||||
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','completed','cancelled')),
|
||||
current_step SMALLINT NOT NULL DEFAULT 1,
|
||||
risk_score NUMERIC(5,2) DEFAULT 0,
|
||||
complexity_index NUMERIC(5,2) DEFAULT 0,
|
||||
infrastructure_load NUMERIC(5,2) DEFAULT 0,
|
||||
service_history NUMERIC(5,2) DEFAULT 0,
|
||||
object_index NUMERIC(5,2) DEFAULT 0,
|
||||
sla_price_monthly NUMERIC(12,2) DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_qsessions_object ON questionnaire_sessions(object_id);
|
||||
CREATE INDEX idx_qsessions_author ON questionnaire_sessions(created_by);
|
||||
|
||||
-- Questionnaire answers
|
||||
CREATE TABLE IF NOT EXISTS questionnaire_answers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id INTEGER NOT NULL REFERENCES questionnaire_sessions(id) ON DELETE CASCADE,
|
||||
step SMALLINT NOT NULL,
|
||||
question_key VARCHAR(100) NOT NULL,
|
||||
answer_value TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_qanswers_session ON questionnaire_answers(session_id);
|
||||
CREATE INDEX idx_qanswers_step ON questionnaire_answers(step);
|
||||
|
||||
-- Questionnaire items
|
||||
CREATE TABLE IF NOT EXISTS questionnaire_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
step INTEGER NOT NULL DEFAULT 1,
|
||||
section VARCHAR(100) NOT NULL DEFAULT '',
|
||||
question_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
label VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(20) NOT NULL DEFAULT 'text' CHECK (type IN ('text','number','select','radio','checkbox','textarea')),
|
||||
options JSONB,
|
||||
required BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
help_text VARCHAR(500) DEFAULT '',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_qitems_step ON questionnaire_items(step);
|
||||
CREATE INDEX idx_qitems_key ON questionnaire_items(question_key);
|
||||
CREATE INDEX idx_qitems_sort ON questionnaire_items(sort_order);
|
||||
|
||||
-- Object passports
|
||||
CREATE TABLE IF NOT EXISTS object_passports (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id INTEGER REFERENCES questionnaire_sessions(id) ON DELETE SET NULL,
|
||||
object_id INTEGER REFERENCES objects(id) ON DELETE SET NULL,
|
||||
object_index NUMERIC(5,2) DEFAULT 0,
|
||||
risk_score NUMERIC(5,2) DEFAULT 0,
|
||||
complexity_index NUMERIC(5,2) DEFAULT 0,
|
||||
sla_price_monthly NUMERIC(12,2) DEFAULT 0,
|
||||
passport_data JSONB,
|
||||
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_passports_object ON object_passports(object_id);
|
||||
CREATE INDEX idx_passports_session ON object_passports(session_id);
|
||||
|
||||
-- SLA contracts
|
||||
CREATE TABLE IF NOT EXISTS sla_contracts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
|
||||
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
|
||||
contract_number VARCHAR(50) DEFAULT '',
|
||||
client_name VARCHAR(255) DEFAULT '',
|
||||
description TEXT,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE,
|
||||
sla_price_monthly NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
penalty_rate NUMERIC(5,2) DEFAULT 0,
|
||||
response_time_p1 INTEGER DEFAULT 4,
|
||||
response_time_p2 INTEGER DEFAULT 8,
|
||||
response_time_p3 INTEGER DEFAULT 24,
|
||||
response_time_hours NUMERIC(5,1),
|
||||
service_level VARCHAR(20) NOT NULL DEFAULT 'business' CHECK (service_level IN ('start','business','enterprise')),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'negotiation' CHECK (status IN ('active','expired','cancelled','negotiation')),
|
||||
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_sla_object ON sla_contracts(object_id);
|
||||
CREATE INDEX idx_sla_status ON sla_contracts(status);
|
||||
CREATE INDEX idx_sla_customer ON sla_contracts(customer_id);
|
||||
|
||||
-- Incidents
|
||||
CREATE TABLE IF NOT EXISTS incidents (
|
||||
id SERIAL PRIMARY KEY,
|
||||
object_id INTEGER NOT NULL REFERENCES objects(id) ON DELETE CASCADE,
|
||||
reported_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
assigned_to INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
severity VARCHAR(4) NOT NULL DEFAULT 'P3' CHECK (severity IN ('P1','P2','P3')),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open','in_progress','resolved','closed')),
|
||||
resolution TEXT,
|
||||
cause VARCHAR(500) DEFAULT '',
|
||||
resolution_time_minutes INTEGER DEFAULT 0,
|
||||
sla_breached BOOLEAN DEFAULT FALSE,
|
||||
resolved_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_incidents_object ON incidents(object_id);
|
||||
CREATE INDEX idx_incidents_severity ON incidents(severity);
|
||||
CREATE INDEX idx_incidents_status ON incidents(status);
|
||||
CREATE INDEX idx_incidents_assigned ON incidents(assigned_to);
|
||||
|
||||
-- Engineer KPI
|
||||
CREATE TABLE IF NOT EXISTS engineer_kpi (
|
||||
id SERIAL PRIMARY KEY,
|
||||
engineer_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
period_start DATE NOT NULL,
|
||||
period_end DATE NOT NULL,
|
||||
sla_compliance NUMERIC(5,2) DEFAULT 0,
|
||||
response_time_score NUMERIC(5,2) DEFAULT 0,
|
||||
resolution_time_score NUMERIC(5,2) DEFAULT 0,
|
||||
diagnosis_accuracy NUMERIC(5,2) DEFAULT 0,
|
||||
reopen_rate NUMERIC(5,2) DEFAULT 0,
|
||||
risk_coverage_score NUMERIC(5,2) DEFAULT 0,
|
||||
documentation_quality NUMERIC(5,2) DEFAULT 0,
|
||||
engineer_score NUMERIC(5,2) DEFAULT 0,
|
||||
grade VARCHAR(20) DEFAULT '',
|
||||
components_json JSONB,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_kpi_engineer ON engineer_kpi(engineer_id);
|
||||
CREATE INDEX idx_kpi_period ON engineer_kpi(period_start, period_end);
|
||||
|
||||
-- SHS records
|
||||
CREATE TABLE IF NOT EXISTS shs_records (
|
||||
id SERIAL PRIMARY KEY,
|
||||
recorded_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
period_start DATE NOT NULL,
|
||||
period_end DATE NOT NULL,
|
||||
sla_stability NUMERIC(5,2) DEFAULT 0,
|
||||
revenue_stability NUMERIC(5,2) DEFAULT 0,
|
||||
retention NUMERIC(5,2) DEFAULT 0,
|
||||
engineer_performance NUMERIC(5,2) DEFAULT 0,
|
||||
incident_stability NUMERIC(5,2) DEFAULT 0,
|
||||
sales_flow NUMERIC(5,2) DEFAULT 0,
|
||||
operational_efficiency NUMERIC(5,2) DEFAULT 0,
|
||||
shs_score NUMERIC(5,2) DEFAULT 0,
|
||||
shs_status VARCHAR(20) DEFAULT '',
|
||||
components_json JSONB,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_shs_period ON shs_records(period_start, period_end);
|
||||
|
||||
-- Blog posts
|
||||
CREATE TABLE IF NOT EXISTS blog_posts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
slug VARCHAR(500) NOT NULL UNIQUE,
|
||||
content TEXT NOT NULL,
|
||||
excerpt VARCHAR(1000) DEFAULT '',
|
||||
category VARCHAR(50) NOT NULL DEFAULT 'audit' CHECK (category IN ('audit','sla','incident','supervision','documentation','risk','cases')),
|
||||
author_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','published','archived')),
|
||||
published_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_blog_slug ON blog_posts(slug);
|
||||
CREATE INDEX idx_blog_category ON blog_posts(category);
|
||||
CREATE INDEX idx_blog_status ON blog_posts(status);
|
||||
CREATE INDEX idx_blog_published ON blog_posts(published_at);
|
||||
|
||||
-- Formula coefficients
|
||||
CREATE TABLE IF NOT EXISTS formula_coefficients (
|
||||
id SERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
value NUMERIC(10,4) NOT NULL DEFAULT 0,
|
||||
description VARCHAR(500) DEFAULT '',
|
||||
formula_ref VARCHAR(255) DEFAULT '',
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
updated_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_coeff_key ON formula_coefficients(key);
|
||||
CREATE INDEX idx_coeff_active ON formula_coefficients(is_active);
|
||||
|
||||
-- Cases
|
||||
CREATE TABLE IF NOT EXISTS cases (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
effect TEXT NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX idx_cases_sort ON cases(sort_order);
|
||||
CREATE INDEX idx_cases_active ON cases(is_active);
|
||||
|
||||
-- Updated_at trigger function
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Apply triggers
|
||||
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_customers_updated_at BEFORE UPDATE ON customers FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_objects_updated_at BEFORE UPDATE ON objects FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_tasks_updated_at BEFORE UPDATE ON tasks FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_reports_updated_at BEFORE UPDATE ON reports FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_qsessions_updated_at BEFORE UPDATE ON questionnaire_sessions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_qitems_updated_at BEFORE UPDATE ON questionnaire_items FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_sla_updated_at BEFORE UPDATE ON sla_contracts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_incidents_updated_at BEFORE UPDATE ON incidents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_blog_updated_at BEFORE UPDATE ON blog_posts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_cases_updated_at BEFORE UPDATE ON cases FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
CREATE TRIGGER update_coeff_updated_at BEFORE UPDATE ON formula_coefficients FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,149 @@
|
||||
-- Seed data for PostgreSQL
|
||||
|
||||
-- Default owner (password: AegisOne2024!)
|
||||
INSERT INTO users (login, password_hash, role, full_name, is_active)
|
||||
VALUES ('owner', '$2b$12$LJ3m4ys3Lk8KqJqKqKqKqO5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'owner', 'System Owner', TRUE)
|
||||
ON CONFLICT (login) DO NOTHING;
|
||||
|
||||
-- Formula coefficients
|
||||
INSERT INTO formula_coefficients (key, value, description, formula_ref) VALUES
|
||||
('risk_score.no_archive', 25, 'Нет архива видеонаблюдения', 'calc_risk_score()'),
|
||||
('risk_score.no_power_backup', 20, 'Нет резервного питания', 'calc_risk_score()'),
|
||||
('risk_score.no_regulations', 15, 'Нет регламента обслуживания', 'calc_risk_score()'),
|
||||
('risk_score.system_failures', 20, 'Частые сбои систем', 'calc_risk_score()'),
|
||||
('risk_score.no_documentation', 10, 'Нет документации', 'calc_risk_score()'),
|
||||
('risk_score.bound_low', 20, 'Граница низкого риска', 'risk_label()'),
|
||||
('risk_score.bound_medium', 50, 'Граница среднего риска', 'risk_label()'),
|
||||
('risk_score.bound_high', 75, 'Граница высокого риска', 'risk_label()'),
|
||||
('risk_multiplier.low', 1.0, 'Множитель низкого риска (0-20)', 'risk_multiplier()'),
|
||||
('risk_multiplier.medium', 1.3, 'Множитель среднего риска (21-50)', 'risk_multiplier()'),
|
||||
('risk_multiplier.high', 1.6, 'Множитель высокого риска (51-75)', 'risk_multiplier()'),
|
||||
('risk_multiplier.critical', 2.0, 'Множитель критического риска (76-100)', 'risk_multiplier()'),
|
||||
('complexity.video_0_20', 10, 'Видеонаблюдение до 20 камер', 'calc_complexity()'),
|
||||
('complexity.video_20_100', 20, 'Видеонаблюдение 20-100 камер', 'calc_complexity()'),
|
||||
('complexity.video_100plus', 35, 'Видеонаблюдение 100+ камер', 'calc_complexity()'),
|
||||
('complexity.access_0_5', 10, 'СКУД до 5 точек', 'calc_complexity()'),
|
||||
('complexity.access_5_20', 20, 'СКУД 5-20 точек', 'calc_complexity()'),
|
||||
('complexity.access_20plus', 30, 'СКУД 20+ точек', 'calc_complexity()'),
|
||||
('complexity.fire_simple', 15, 'Пожарная сигнализация простая', 'calc_complexity()'),
|
||||
('complexity.fire_medium', 25, 'Пожарная сигнализация средняя', 'calc_complexity()'),
|
||||
('complexity.fire_complex', 40, 'Пожарная сигнализация сложная (>5000 м²)', 'calc_complexity()'),
|
||||
('complexity.it', 10, 'IT-инфраструктура', 'calc_complexity()'),
|
||||
('infra.server_none', 15, 'Нет сервера', 'calc_infrastructure_load()'),
|
||||
('infra.server_weak', 10, 'Слабый сервер', 'calc_infrastructure_load()'),
|
||||
('infra.network_unstable', 20, 'Нестабильная сеть', 'calc_infrastructure_load()'),
|
||||
('infra.network_partial', 10, 'Частичная сеть', 'calc_infrastructure_load()'),
|
||||
('infra.power_none', 20, 'Нет UPS', 'calc_infrastructure_load()'),
|
||||
('infra.power_weak', 10, 'Слабый UPS', 'calc_infrastructure_load()'),
|
||||
('history.none', 30, 'Нет обслуживания', 'calc_service_history()'),
|
||||
('history.irregular', 20, 'Нерегулярное обслуживание', 'calc_service_history()'),
|
||||
('history.formal', 10, 'Формальный подрядчик', 'calc_service_history()'),
|
||||
('history.sla', 0, 'Есть SLA', 'calc_service_history()'),
|
||||
('object_index.risk_weight', 0.4, 'Вес Risk Score', 'calc_object_index()'),
|
||||
('object_index.complexity_weight', 0.3, 'Вес Complexity', 'calc_object_index()'),
|
||||
('object_index.infra_weight', 0.2, 'Вес Infra Load', 'calc_object_index()'),
|
||||
('object_index.history_weight', 0.1, 'Вес Service History', 'calc_object_index()'),
|
||||
('sla_price.base_cost', 15000, 'Базовая стоимость инженера', 'calc_sla_price()'),
|
||||
('sla_price.region_default', 1.0, 'Региональный коэффициент по умолчанию', 'calc_sla_price()'),
|
||||
('engineer.score.sla_weight', 0.25, 'Вес SLA Compliance', 'calc_engineer_score()'),
|
||||
('engineer.score.response_weight', 0.20, 'Вес Response Time', 'calc_engineer_score()'),
|
||||
('engineer.score.resolution_weight', 0.20, 'Вес Resolution Time', 'calc_engineer_score()'),
|
||||
('engineer.score.diagnosis_weight', 0.15, 'Вес Diagnosis Accuracy', 'calc_engineer_score()'),
|
||||
('engineer.score.reopen_weight', 0.10, 'Вес Reopen Rate', 'calc_engineer_score()'),
|
||||
('engineer.score.risk_coverage_weight', 0.10, 'Вес Risk Coverage', 'calc_engineer_score()'),
|
||||
('engineer.grade.senior', 90, 'Граница Senior', 'engineer_grade()'),
|
||||
('engineer.grade.strong', 80, 'Граница Strong', 'engineer_grade()'),
|
||||
('engineer.grade.middle', 70, 'Граница Middle', 'engineer_grade()'),
|
||||
('ecs.sla_weight', 0.30, 'Вес SLA Control', 'calc_engineer_control_score()'),
|
||||
('ecs.task_dist_weight', 0.25, 'Вес Task Distribution', 'calc_engineer_control_score()'),
|
||||
('ecs.incident_red_weight', 0.20, 'Вес Incident Reduction', 'calc_engineer_control_score()'),
|
||||
('ecs.team_perf_weight', 0.15, 'Вес Team Performance', 'calc_engineer_control_score()'),
|
||||
('ecs.response_coord_weight', 0.10, 'Вес Response Coordination', 'calc_engineer_control_score()'),
|
||||
('shs.sla_stability_weight', 0.22, 'Вес SLA Stability', 'calc_shs()'),
|
||||
('shs.revenue_stability_weight', 0.18, 'Вес Revenue Stability', 'calc_shs()'),
|
||||
('shs.retention_weight', 0.18, 'Вес Retention', 'calc_shs()'),
|
||||
('shs.engineer_perf_weight', 0.15, 'Вес Engineer Performance', 'calc_shs()'),
|
||||
('shs.incident_stability_weight', 0.12, 'Вес Incident Stability', 'calc_shs()'),
|
||||
('shs.sales_flow_weight', 0.10, 'Вес Sales Flow', 'calc_shs()'),
|
||||
('shs.operational_eff_weight', 0.05, 'Вес Operational Efficiency', 'calc_shs()'),
|
||||
('shs.zone_growth', 85, 'Граница зоны Growth', 'shs_status(), shs_zone()'),
|
||||
('shs.zone_stable', 70, 'Граница зоны Stable', 'shs_status(), shs_zone()'),
|
||||
('shs.zone_risk', 50, 'Граница зоны Risk', 'shs_status(), shs_zone()'),
|
||||
('shs.delta_warning', 5, 'Порог предупреждения ΔSHS', 'shs_delta()'),
|
||||
('shs.delta_critical', 10, 'Порог критического ΔSHS', 'shs_delta()'),
|
||||
('ceo_shs.mrr_weight', 0.25, 'Вес MRR Growth', 'calc_ceo_shs()'),
|
||||
('ceo_shs.sla_weight', 0.20, 'Вес SLA Compliance', 'calc_ceo_shs()'),
|
||||
('ceo_shs.retention_weight', 0.20, 'Вес Retention', 'calc_ceo_shs()'),
|
||||
('ceo_shs.productivity_weight', 0.15, 'Вес Productivity', 'calc_ceo_shs()'),
|
||||
('ceo_shs.conversion_weight', 0.10, 'Вес Conversion', 'calc_ceo_shs()'),
|
||||
('ceo_shs.incident_weight', 0.10, 'Вес Incident Stability', 'calc_ceo_shs()'),
|
||||
('ssi.breach_severity_p1', 1.0, 'Severity P1', 'calc_breach_severity()'),
|
||||
('ssi.breach_severity_p2', 0.5, 'Severity P2', 'calc_breach_severity()'),
|
||||
('ssi.breach_severity_p3', 0.2, 'Severity P3', 'calc_breach_severity()'),
|
||||
('isi.severity_p1', 1.0, 'Severity P1', 'calc_incident_stability_index()'),
|
||||
('isi.severity_p2', 0.5, 'Severity P2', 'calc_incident_stability_index()'),
|
||||
('isi.severity_p3', 0.2, 'Severity P3', 'calc_incident_stability_index()'),
|
||||
('rules.shs_threshold', 70, 'Порог SHS', 'check_automation_rules()'),
|
||||
('rules.sla_threshold', 90, 'Порог SLA Compliance', 'check_automation_rules()'),
|
||||
('rules.retention_threshold', 90, 'Порог Retention', 'check_automation_rules()'),
|
||||
('bonus.score_90plus', 90, 'Граница премии 90+', 'calc_bonus_percent()'),
|
||||
('bonus.score_80_89', 80, 'Граница премии 80-89', 'calc_bonus_percent()'),
|
||||
('bonus.premium_90plus', 20, 'Премия при Score >= 90 (%)', 'calc_bonus_percent()'),
|
||||
('bonus.premium_80_89', 10, 'Премия при Score 80-89 (%)', 'calc_bonus_percent()'),
|
||||
('bonus.premium_below_80', 0, 'Премия при Score < 80 (%)', 'calc_bonus_percent()'),
|
||||
('retention.key_client_penalty', 0.1, 'Штраф за потерю ключевого клиента', 'calc_retention_key_client()')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- Cases
|
||||
INSERT INTO cases (title, text, effect, sort_order) VALUES
|
||||
('Склад крупной торговой сети', 'Выявлено: слепые зоны в зонах разгрузки, недостаточное разрешение камер в зонах погрузки/разгрузки. Одна камера не исправна, у двух не настроена запись.', 'предотвращение убытков на сумму более 5 млн рублей в год.', 1),
|
||||
('Производство пищевых продуктов', 'Неправильная настройка СКУД позволяла несанкционированный доступ в производственные помещения, создавая риск нарушения норм СанПин.', 'снижение риска порчи продукции и утечки конфиденциальной информации.', 2),
|
||||
('Банковский офис', 'Недостатки в системе ОПС приводили к невозможности срабатывания или задержкам в реагировании при ЧП.', 'сокращение рисков утраты имущества и ценностей, повышение уровня безопасности.', 3),
|
||||
('Торгово-развлекательный центр', 'Метод «Тайный покупатель» выявил: охрана не реагирует на срабатывание металлодетекторов, оставляет посты без присмотра. Камеры на парковке не охватывают часть машиномест.', 'снижение риска краж, повышение дисциплины службы охраны.', 4),
|
||||
('Гостиница', 'СКУД не синхронизирована с системой бронирования — доступ в занятые номера. ОПС в части номеров отключена. Видеокамеры имеют мёртвые зоны.', 'устранение репутационных рисков и риска хищения личных вещей гостей.', 5),
|
||||
('Логистический центр', 'Отсутствие видеоконтроля на участках приёмки и отгрузки приводило к систематическим хищениям. СКУД на въезде не фиксирует данные транспортных средств.', 'сокращение потерь товара на 15%.', 6)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Questionnaire items
|
||||
INSERT INTO questionnaire_items (step, section, question_key, label, type, options, required, sort_order, help_text) VALUES
|
||||
(1, 'Коммерческий профиль объекта', 'object_name', 'Название организации / объекта', 'text', NULL, TRUE, 1, ''),
|
||||
(1, 'Коммерческий профиль объекта', 'object_type', 'Тип объекта', 'select', '["","Гостиница","Склад","Производство","ТЦ","Офис","БЦ","Медицина","Образование","Другое"]', FALSE, 2, ''),
|
||||
(1, 'Коммерческий профиль объекта', 'address', 'Адрес объекта', 'text', NULL, FALSE, 3, ''),
|
||||
(1, 'Коммерческий профиль объекта', 'area', 'Площадь (м²)', 'number', NULL, FALSE, 4, ''),
|
||||
(1, 'Коммерческий профиль объекта', 'employees', 'Количество сотрудников', 'number', NULL, FALSE, 5, ''),
|
||||
(1, 'Коммерческий профиль объекта', 'contact_person', 'Контактное лицо', 'text', NULL, FALSE, 6, ''),
|
||||
(1, 'Коммерческий профиль объекта', 'contact_phone', 'Телефон', 'text', NULL, FALSE, 7, ''),
|
||||
(1, 'Коммерческий профиль объекта', 'region_factor', 'Региональный коэффициент', 'select', '{"1.0":"Краснодар (1.0)","1.1":"Краснодарский край (1.1)","1.3":"РФ (1.3)","1.6":"РФ удалённые (1.6)","1.8":"Москва (1.8)"}', FALSE, 8, ''),
|
||||
(2, 'Видеонаблюдение', 'has_video', 'Есть система видеонаблюдения?', 'radio', '{"1":"Да","0":"Нет"}', FALSE, 1, ''),
|
||||
(2, 'Видеонаблюдение', 'camera_count', 'Количество камер', 'number', NULL, FALSE, 2, ''),
|
||||
(2, 'Видеонаблюдение', 'video_type', 'Тип системы', 'select', '["","ip","analog","mixed"]', FALSE, 3, ''),
|
||||
(2, 'Видеонаблюдение', 'archive_depth', 'Глубина архива', 'select', '{"7":"7 дней","14":"14 дней","30":"30+ дней"}', FALSE, 4, ''),
|
||||
(2, 'СКУД', 'has_acs', 'Есть система СКУД?', 'radio', '{"1":"Да","0":"Нет"}', FALSE, 5, ''),
|
||||
(2, 'СКУД', 'access_points', 'Точек доступа', 'number', NULL, FALSE, 6, ''),
|
||||
(2, 'СКУД', 'acs_vendor', 'Производитель СКУД', 'text', NULL, FALSE, 7, ''),
|
||||
(2, 'Пожарная сигнализация', 'has_fire', 'Есть пожарная сигнализация?', 'radio', '{"1":"Да","0":"Нет"}', FALSE, 8, ''),
|
||||
(2, 'Пожарная сигнализация', 'fire_type', 'Сложность системы', 'select', '{"simple":"Простая","medium":"Средняя","complex":"Сложная (>5000 м²)"}', FALSE, 9, ''),
|
||||
(2, 'Инфраструктура', 'server_state', 'Состояние сервера', 'select', '{"ok":"Нормальный","weak":"Слабый","none":"Нет"}', FALSE, 10, ''),
|
||||
(2, 'Инфраструктура', 'network_state', 'Состояние сети', 'select', '{"stable":"Стабильная","partial":"Частично","unstable":"Нестабильная"}', FALSE, 11, ''),
|
||||
(2, 'Инфраструктура', 'power_state', 'Электропитание (UPS)', 'select', '{"ok":"Нормальное","weak":"Слабый UPS","none":"Нет UPS"}', FALSE, 12, ''),
|
||||
(3, 'Эксплуатационная модель', 'service_state', 'Как обслуживается объект сейчас?', 'select', '{"none":"Не обслуживается","irregular":"Нерегулярно","formal":"Формальный подрядчик","sla":"Есть SLA"}', FALSE, 1, ''),
|
||||
(3, 'Эксплуатационная модель', 'has_regulations', 'Есть ли регламент?', 'checkbox', NULL, FALSE, 2, ''),
|
||||
(3, 'Эксплуатационная модель', 'problems', 'Отметьте частые проблемы', 'checkbox', '["Не работают камеры","Пропадает архив","Зависает СКУД","Ошибки пожарки","Нет реакции подрядчика","Нет понимания состояния систем"]', FALSE, 3, ''),
|
||||
(3, 'Эксплуатационная модель', 'resolution_time', 'Среднее время устранения', 'select', '{"2h":"До 2 часов","24h":"До суток","days":"Несколько дней"}', FALSE, 4, ''),
|
||||
(3, 'Эксплуатационная модель', 'controller', 'Кто контролирует систему?', 'select', '{"engineer":"Штатный инженер","contractor":"Подрядчик","nobody":"Никто"}', FALSE, 5, ''),
|
||||
(4, 'Risk Assessment', 'no_archive', 'Нет архива видеонаблюдения (+25)', 'checkbox', NULL, FALSE, 1, ''),
|
||||
(4, 'Risk Assessment', 'no_power', 'Нет резервного питания (+20)', 'checkbox', NULL, FALSE, 2, ''),
|
||||
(4, 'Risk Assessment', 'no_regulations', 'Нет регламента обслуживания (+15)', 'checkbox', NULL, FALSE, 3, ''),
|
||||
(4, 'Risk Assessment', 'frequent_failures', 'Частые сбои систем (+20)', 'checkbox', NULL, FALSE, 4, ''),
|
||||
(4, 'Risk Assessment', 'no_documentation', 'Нет документации (+10)', 'checkbox', NULL, FALSE, 5, ''),
|
||||
(4, 'Risk Assessment', 'consequences', 'Потенциальные последствия', 'checkbox', '["loss_of_evidence","shutdown","fines","no_investigation","access_fail"]', FALSE, 6, ''),
|
||||
(4, 'Risk Assessment', 'confidence', 'Уровень уверенности в системе', 'select', '{"high":"Высокая","medium":"Средняя","low":"Низкая"}', FALSE, 7, ''),
|
||||
(5, 'Расчёт SLA', 'sla_level', 'Предпочтительный уровень обслуживания', 'select', '{"start":"Базовый (Start SLA)","business":"Стандарт (Business SLA)","enterprise":"Расширенный (Enterprise SLA)"}', FALSE, 1, ''),
|
||||
(5, 'Расчёт SLA', 'visit_frequency', 'Интенсивность обслуживания', 'select', '{"1":"1 выезд / мес","2":"2 выезда / мес","incident":"По инцидентам"}', FALSE, 2, ''),
|
||||
(5, 'Расчёт SLA', 'reaction_time', 'Требуемое время реакции', 'select', '{"24":"24 часа","4":"4 часа","2":"2 часа"}', FALSE, 3, ''),
|
||||
(5, 'Расчёт SLA', 'extra_247', '24/7 поддержка', 'checkbox', NULL, FALSE, 4, ''),
|
||||
(5, 'Расчёт SLA', 'extra_emergency', 'Аварийные выезды', 'checkbox', NULL, FALSE, 5, ''),
|
||||
(5, 'Расчёт SLA', 'extra_audit', 'Аудит', 'checkbox', NULL, FALSE, 6, ''),
|
||||
(5, 'Расчёт SLA', 'extra_docs', 'Документация', 'checkbox', NULL, FALSE, 7, ''),
|
||||
(5, 'Расчёт SLA', 'extra_contractors', 'Контроль подрядчиков', 'checkbox', NULL, FALSE, 8, '')
|
||||
ON CONFLICT (question_key) DO NOTHING;
|
||||
Reference in New Issue
Block a user