-- 1. Π’Π°Π±Π»ΠΈΡΠ° ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ
CREATE TABLE users (
id SERIAL PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20) UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ users
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_users_active ON users(is_active) WHERE is_active = TRUE;
CREATE INDEX idx_users_name ON users(last_name, first_name);
-- 2. Π’Π°Π±Π»ΠΈΡΠ° ΡΠΏΠ΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΉ
CREATE TABLE specializations (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ΠΠ½Π΄Π΅ΠΊΡ Π΄Π»Ρ ΠΏΠΎΠΈΡΠΊΠ° ΠΏΠΎ Π½Π°Π·Π²Π°Π½ΠΈΡ ΡΠΏΠ΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ
CREATE INDEX idx_specializations_name ON specializations(name);
-- 3. Π’Π°Π±Π»ΠΈΡΠ° Π²ΡΠ°ΡΠ΅ΠΉ
CREATE TABLE doctors (
id SERIAL PRIMARY KEY,
user_id INTEGER UNIQUE NOT NULL REFERENCES users(id) ON DELETE CASCADE,
specialization_id INTEGER REFERENCES specializations(id),
license_number VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ doctors
CREATE INDEX idx_doctors_user_id ON doctors(user_id);
CREATE INDEX idx_doctors_specialization_id ON doctors(specialization_id);
CREATE INDEX idx_doctors_license ON doctors(license_number) WHERE license_number IS NOT NULL;
-- 4. Π’Π°Π±Π»ΠΈΡΠ° ΠΊΠ»ΠΈΠ΅Π½ΡΠΎΠ²
CREATE TABLE clients (
id SERIAL PRIMARY KEY,
user_id INTEGER UNIQUE NOT NULL REFERENCES users(id) ON DELETE CASCADE,
birth_date DATE,
gender VARCHAR(10),
address TEXT,
insurance_number VARCHAR(50),
registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_visit_date TIMESTAMP,
notes TEXT,
is_vip BOOLEAN DEFAULT FALSE,
discount_rate INTEGER DEFAULT 0 CHECK (discount_rate BETWEEN 0 AND 100)
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ clients
CREATE INDEX idx_clients_user_id ON clients(user_id);
CREATE INDEX idx_clients_birth_date ON clients(birth_date);
CREATE INDEX idx_clients_last_visit ON clients(last_visit_date) WHERE last_visit_date IS NOT NULL;
CREATE INDEX idx_clients_vip ON clients(is_vip) WHERE is_vip = TRUE;
CREATE INDEX idx_clients_insurance ON clients(insurance_number) WHERE insurance_number IS NOT NULL;
-- 5. Π’Π°Π±Π»ΠΈΡΠ° Π³ΡΠ°ΡΠΈΠΊΠ° ΡΠ°Π±ΠΎΡΡ Π²ΡΠ°ΡΠ΅ΠΉ
CREATE TABLE doctor_schedules (
id SERIAL PRIMARY KEY,
doctor_id INTEGER NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
day_of_week SMALLINT NOT NULL CHECK (day_of_week BETWEEN 1 AND 7),
start_time TIME NOT NULL,
end_time TIME NOT NULL,
is_recurring BOOLEAN DEFAULT TRUE,
valid_from DATE NOT NULL DEFAULT CURRENT_DATE,
valid_to DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT valid_time_range CHECK (end_time > start_time),
UNIQUE (doctor_id, day_of_week, valid_from)
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ doctor_schedules
CREATE INDEX idx_schedules_doctor ON doctor_schedules(doctor_id);
CREATE INDEX idx_schedules_current ON doctor_schedules(doctor_id, day_of_week)
WHERE is_recurring = TRUE AND (valid_to IS NULL OR valid_to >= CURRENT_DATE);
CREATE INDEX idx_schedules_date_range ON doctor_schedules(valid_from, valid_to);
-- 6. Π’Π°Π±Π»ΠΈΡΠ° ΠΈΡΠΊΠ»ΡΡΠ΅Π½ΠΈΠΉ Π² Π³ΡΠ°ΡΠΈΠΊΠ΅
CREATE TABLE schedule_exceptions (
id SERIAL PRIMARY KEY,
doctor_id INTEGER NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
exception_date DATE NOT NULL,
is_working_day BOOLEAN NOT NULL,
start_time TIME,
end_time TIME,
reason TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT valid_exception_times CHECK (
(is_working_day = FALSE AND start_time IS NULL AND end_time IS NULL) OR
(is_working_day = TRUE AND start_time IS NOT NULL AND end_time IS NOT NULL AND end_time > start_time)
),
UNIQUE (doctor_id, exception_date)
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ schedule_exceptions
CREATE INDEX idx_exceptions_doctor ON schedule_exceptions(doctor_id);
CREATE INDEX idx_exceptions_date ON schedule_exceptions(exception_date);
-- 7. Π’Π°Π±Π»ΠΈΡΠ° Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΡΠ»ΠΎΡΠΎΠ²
CREATE TABLE time_slots (
id SERIAL PRIMARY KEY,
doctor_id INTEGER NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
date DATE NOT NULL,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'available' CHECK (status IN ('available', 'booked', 'completed', 'cancelled')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT valid_slot_time CHECK (end_time > start_time),
UNIQUE (doctor_id, start_time)
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ time_slots
CREATE INDEX idx_slots_doctor ON time_slots(doctor_id);
CREATE INDEX idx_slots_date ON time_slots(date);
CREATE INDEX idx_slots_status ON time_slots(status);
CREATE INDEX idx_slots_available ON time_slots(doctor_id, start_time)
WHERE status = 'available' AND start_time > CURRENT_TIMESTAMP;
-- 8. Π’Π°Π±Π»ΠΈΡΠ° Π·Π°ΠΏΠΈΡΠ΅ΠΉ Π½Π° ΠΏΡΠΈΠ΅ΠΌ
CREATE TABLE appointments (
id SERIAL PRIMARY KEY,
slot_id INTEGER NOT NULL REFERENCES time_slots(id) ON DELETE CASCADE,
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'completed', 'cancelled', 'no_show')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
notes TEXT,
UNIQUE (slot_id)
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ appointments
CREATE INDEX idx_appointments_client ON appointments(client_id);
CREATE INDEX idx_appointments_status ON appointments(status);
CREATE INDEX idx_appointments_slot ON appointments(slot_id);
-- 9. Π’Π°Π±Π»ΠΈΡΠ° ΠΎΡΠΌΠ΅Π½Π΅Π½Π½ΡΡ ΠΏΡΠΈΠ΅ΠΌΠΎΠ² (ΡΠΎΡΡ-ΠΌΠ°ΠΆΠΎΡΡ)
CREATE TABLE cancellations (
id SERIAL PRIMARY KEY,
appointment_id INTEGER REFERENCES appointments(id) ON DELETE SET NULL,
doctor_id INTEGER NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
cancellation_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
reason TEXT NOT NULL,
is_compensated BOOLEAN DEFAULT FALSE,
compensation_details TEXT
);
-- ΠΠ½Π΄Π΅ΠΊΡΡ Π΄Π»Ρ cancellations
CREATE INDEX idx_cancellations_doctor ON cancellations(doctor_id);
CREATE INDEX idx_cancellations_time ON cancellations(cancellation_time);
CREATE INDEX idx_cancellations_compensated ON cancellations(is_compensated) WHERE is_compensated = FALSE;
-- Π’ΡΠΈΠ³Π³Π΅ΡΡ Π΄Π»Ρ Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ Π²ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ ΠΌΠ΅ΡΠΎΠΊ
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ΠΡΠΈΠΌΠ΅Π½ΡΠ΅ΠΌ ΡΡΠΈΠ³Π³Π΅ΡΡ ΠΊ ΡΠ°Π±Π»ΠΈΡΠ°ΠΌ
CREATE TRIGGER users_update_timestamp
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
CREATE TRIGGER doctors_update_timestamp
BEFORE UPDATE ON doctors
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
CREATE TRIGGER appointments_update_timestamp
BEFORE UPDATE ON appointments
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
CREATE TRIGGER schedules_update_timestamp
BEFORE UPDATE ON doctor_schedules
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
-- Π’ΡΠΈΠ³Π³Π΅Ρ Π΄Π»Ρ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ last_visit_date Ρ ΠΊΠ»ΠΈΠ΅Π½ΡΠ°
CREATE OR REPLACE FUNCTION update_client_visit()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.status = 'completed' THEN
UPDATE clients
SET last_visit_date = CURRENT_TIMESTAMP
WHERE id = NEW.client_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER appointments_update_visit
AFTER UPDATE ON appointments
FOR EACH ROW EXECUTE FUNCTION update_client_visit();