| 1 | CREATE TYPE user_role_enum AS ENUM (
|
|---|
| 2 | 'client',
|
|---|
| 3 | 'staff',
|
|---|
| 4 | 'owner',
|
|---|
| 5 | 'admin'
|
|---|
| 6 | );
|
|---|
| 7 |
|
|---|
| 8 | CREATE TYPE appointment_status_enum AS ENUM (
|
|---|
| 9 | 'pending',
|
|---|
| 10 | 'confirmed',
|
|---|
| 11 | 'completed',
|
|---|
| 12 | 'cancelled'
|
|---|
| 13 | );
|
|---|
| 14 |
|
|---|
| 15 | CREATE TYPE staff_role_type_enum AS ENUM (
|
|---|
| 16 | 'hairdresser',
|
|---|
| 17 | 'nail_tech',
|
|---|
| 18 | 'esthetician',
|
|---|
| 19 | 'makeup_artist',
|
|---|
| 20 | 'receptionist'
|
|---|
| 21 | );
|
|---|
| 22 |
|
|---|
| 23 | CREATE TYPE discount_type_enum AS ENUM (
|
|---|
| 24 | 'percentage',
|
|---|
| 25 | 'fixed'
|
|---|
| 26 | );
|
|---|
| 27 |
|
|---|
| 28 | CREATE TYPE payment_method_enum AS ENUM (
|
|---|
| 29 | 'cash',
|
|---|
| 30 | 'card',
|
|---|
| 31 | 'loyalty_points'
|
|---|
| 32 | );
|
|---|
| 33 |
|
|---|
| 34 | CREATE TYPE day_of_week_enum AS ENUM (
|
|---|
| 35 | 'monday',
|
|---|
| 36 | 'tuesday',
|
|---|
| 37 | 'wednesday',
|
|---|
| 38 | 'thursday',
|
|---|
| 39 | 'friday',
|
|---|
| 40 | 'saturday',
|
|---|
| 41 | 'sunday'
|
|---|
| 42 | );
|
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 | CREATE TABLE company_category
|
|---|
| 47 | (
|
|---|
| 48 | company_category_id SERIAL PRIMARY KEY,
|
|---|
| 49 | category_name VARCHAR(100) NOT NULL UNIQUE
|
|---|
| 50 | );
|
|---|
| 51 |
|
|---|
| 52 | CREATE TABLE company
|
|---|
| 53 | (
|
|---|
| 54 | company_id SERIAL PRIMARY KEY,
|
|---|
| 55 | name VARCHAR(150) NOT NULL,
|
|---|
| 56 | email VARCHAR(255) NOT NULL UNIQUE,
|
|---|
| 57 | phone VARCHAR(30) UNIQUE,
|
|---|
| 58 | is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|---|
| 59 | logo_url TEXT,
|
|---|
| 60 | description TEXT,
|
|---|
| 61 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|---|
| 62 | );
|
|---|
| 63 |
|
|---|
| 64 | CREATE TABLE company_company_category
|
|---|
| 65 | (
|
|---|
| 66 | company_id INT NOT NULL REFERENCES company (company_id) ON DELETE CASCADE,
|
|---|
| 67 | company_category_id INT NOT NULL REFERENCES company_category (company_category_id) ON DELETE CASCADE,
|
|---|
| 68 | PRIMARY KEY (company_id, company_category_id)
|
|---|
| 69 | );
|
|---|
| 70 |
|
|---|
| 71 | CREATE TABLE company_location
|
|---|
| 72 | (
|
|---|
| 73 | location_id SERIAL PRIMARY KEY,
|
|---|
| 74 | company_id INT NOT NULL REFERENCES company (company_id) ON DELETE CASCADE,
|
|---|
| 75 | address VARCHAR(255) NOT NULL,
|
|---|
| 76 | city VARCHAR(100) NOT NULL,
|
|---|
| 77 | phone VARCHAR(30)
|
|---|
| 78 | );
|
|---|
| 79 |
|
|---|
| 80 | CREATE TABLE business_hours
|
|---|
| 81 | (
|
|---|
| 82 | hours_id SERIAL PRIMARY KEY,
|
|---|
| 83 | location_id INT NOT NULL REFERENCES company_location (location_id) ON DELETE SET DEFAULT,
|
|---|
| 84 | day_of_week day_of_week_enum NOT NULL,
|
|---|
| 85 | is_closed BOOLEAN NOT NULL DEFAULT FALSE,
|
|---|
| 86 | open_time TIME,
|
|---|
| 87 | close_time TIME,
|
|---|
| 88 | CONSTRAINT chk_business_hours_order CHECK (close_time > open_time),
|
|---|
| 89 | CONSTRAINT chk_closed_times CHECK (
|
|---|
| 90 | (is_closed = TRUE AND open_time IS NULL AND close_time IS NULL) OR
|
|---|
| 91 | (is_closed = FALSE AND open_time IS NOT NULL AND close_time IS NOT NULL)
|
|---|
| 92 | ),
|
|---|
| 93 | UNIQUE (location_id, day_of_week)
|
|---|
| 94 | );
|
|---|
| 95 |
|
|---|
| 96 |
|
|---|
| 97 |
|
|---|
| 98 | CREATE TABLE "user"
|
|---|
| 99 | (
|
|---|
| 100 | user_id SERIAL PRIMARY KEY,
|
|---|
| 101 | first_name VARCHAR(100) NOT NULL,
|
|---|
| 102 | last_name VARCHAR(100) NOT NULL,
|
|---|
| 103 | email VARCHAR(255) NOT NULL UNIQUE,
|
|---|
| 104 | password_hash TEXT NOT NULL,
|
|---|
| 105 | role user_role_enum NOT NULL,
|
|---|
| 106 | is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|---|
| 107 | profile_image_url TEXT,
|
|---|
| 108 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|---|
| 109 | );
|
|---|
| 110 |
|
|---|
| 111 | CREATE TABLE client
|
|---|
| 112 | (
|
|---|
| 113 | client_id INT PRIMARY KEY REFERENCES "user" (user_id) ON DELETE CASCADE,
|
|---|
| 114 | loyalty_points INT NOT NULL DEFAULT 0 CHECK (loyalty_points >= 0),
|
|---|
| 115 | date_of_birth DATE,
|
|---|
| 116 | phone VARCHAR(30),
|
|---|
| 117 | notes TEXT
|
|---|
| 118 | );
|
|---|
| 119 |
|
|---|
| 120 | CREATE TABLE owner
|
|---|
| 121 | (
|
|---|
| 122 | owner_id INT PRIMARY KEY REFERENCES "user" (user_id) ON DELETE CASCADE,
|
|---|
| 123 | company_id INT NOT NULL REFERENCES company (company_id),
|
|---|
| 124 | owner_since DATE NOT NULL DEFAULT CURRENT_DATE
|
|---|
| 125 | );
|
|---|
| 126 |
|
|---|
| 127 | CREATE TABLE staff
|
|---|
| 128 | (
|
|---|
| 129 | staff_id INT PRIMARY KEY REFERENCES "user" (user_id) ON DELETE CASCADE,
|
|---|
| 130 | location_id INT NOT NULL REFERENCES company_location (location_id),
|
|---|
| 131 | date_hired DATE NOT NULL DEFAULT CURRENT_DATE,
|
|---|
| 132 | hourly_rate NUMERIC(10, 2) CHECK (hourly_rate >= 0)
|
|---|
| 133 | );
|
|---|
| 134 |
|
|---|
| 135 | CREATE TABLE staff_type
|
|---|
| 136 | (
|
|---|
| 137 | staff_type_id SERIAL PRIMARY KEY,
|
|---|
| 138 | staff_id INT NOT NULL REFERENCES staff (staff_id) ON DELETE CASCADE,
|
|---|
| 139 | role_type staff_role_type_enum NOT NULL,
|
|---|
| 140 | specialty VARCHAR(150),
|
|---|
| 141 | certification VARCHAR(150),
|
|---|
| 142 | years_experience SMALLINT CHECK (years_experience >= 0),
|
|---|
| 143 | extra_info TEXT,
|
|---|
| 144 | UNIQUE (staff_id, role_type)
|
|---|
| 145 | );
|
|---|
| 146 |
|
|---|
| 147 |
|
|---|
| 148 |
|
|---|
| 149 | CREATE TABLE service_category
|
|---|
| 150 | (
|
|---|
| 151 | service_category_id SERIAL PRIMARY KEY,
|
|---|
| 152 | category_name VARCHAR(100) NOT NULL UNIQUE,
|
|---|
| 153 | description TEXT
|
|---|
| 154 | );
|
|---|
| 155 |
|
|---|
| 156 | CREATE TABLE service
|
|---|
| 157 | (
|
|---|
| 158 | service_id SERIAL PRIMARY KEY,
|
|---|
| 159 | company_id INT NOT NULL REFERENCES company (company_id) ON DELETE SET NULL,
|
|---|
| 160 | service_category_id INT NOT NULL REFERENCES service_category (service_category_id),
|
|---|
| 161 | service_name VARCHAR(150) NOT NULL,
|
|---|
| 162 | duration_minutes SMALLINT NOT NULL CHECK (duration_minutes > 0 AND duration_minutes % 15 = 0),
|
|---|
| 163 | price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
|
|---|
| 164 | is_active BOOLEAN NOT NULL DEFAULT TRUE
|
|---|
| 165 | );
|
|---|
| 166 |
|
|---|
| 167 | CREATE TABLE staff_service
|
|---|
| 168 | (
|
|---|
| 169 | staff_id INT NOT NULL REFERENCES staff (staff_id) ON DELETE CASCADE,
|
|---|
| 170 | service_id INT NOT NULL REFERENCES service (service_id) ON DELETE CASCADE,
|
|---|
| 171 | notes TEXT,
|
|---|
| 172 | PRIMARY KEY (staff_id, service_id)
|
|---|
| 173 | );
|
|---|
| 174 |
|
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 | CREATE TABLE staff_availability
|
|---|
| 178 | (
|
|---|
| 179 | availability_id SERIAL PRIMARY KEY,
|
|---|
| 180 | staff_id INT NOT NULL REFERENCES staff (staff_id) ON DELETE CASCADE,
|
|---|
| 181 | day_of_week day_of_week_enum NOT NULL,
|
|---|
| 182 | start_time TIME NOT NULL,
|
|---|
| 183 | end_time TIME NOT NULL,
|
|---|
| 184 | CONSTRAINT chk_availability_order CHECK (end_time > start_time),
|
|---|
| 185 | UNIQUE (staff_id, day_of_week)
|
|---|
| 186 | );
|
|---|
| 187 |
|
|---|
| 188 | CREATE TABLE blocked_time
|
|---|
| 189 | (
|
|---|
| 190 | block_id SERIAL PRIMARY KEY,
|
|---|
| 191 | staff_id INT NOT NULL REFERENCES staff (staff_id) ON DELETE CASCADE,
|
|---|
| 192 | start_datetime TIMESTAMPTZ NOT NULL,
|
|---|
| 193 | end_datetime TIMESTAMPTZ NOT NULL,
|
|---|
| 194 | reason TEXT,
|
|---|
| 195 | CONSTRAINT chk_blocked_time_order CHECK (end_datetime > start_datetime)
|
|---|
| 196 | );
|
|---|
| 197 |
|
|---|
| 198 | CREATE TABLE appointment
|
|---|
| 199 | (
|
|---|
| 200 | appointment_id SERIAL PRIMARY KEY,
|
|---|
| 201 | client_id INT NOT NULL REFERENCES client (client_id) ON DELETE SET DEFAULT,
|
|---|
| 202 | staff_id INT NOT NULL REFERENCES staff (staff_id) ON DELETE SET DEFAULT,
|
|---|
| 203 | location_id INT NOT NULL REFERENCES company_location (location_id) ON DELETE SET DEFAULT,
|
|---|
| 204 | appointment_date DATE NOT NULL,
|
|---|
| 205 | appointment_time TIME NOT NULL,
|
|---|
| 206 | end_time TIME NOT NULL,
|
|---|
| 207 | status appointment_status_enum NOT NULL DEFAULT 'pending',
|
|---|
| 208 | notes TEXT,
|
|---|
| 209 | booked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|---|
| 210 | cancelled_at TIMESTAMPTZ,
|
|---|
| 211 | cancellation_reason TEXT,
|
|---|
| 212 | CONSTRAINT chk_appointment_time_order CHECK (end_time > appointment_time),
|
|---|
| 213 | CONSTRAINT chk_cancellation CHECK (
|
|---|
| 214 | (status = 'cancelled' AND cancelled_at IS NOT NULL) OR
|
|---|
| 215 | (status <> 'cancelled' AND cancelled_at IS NULL)
|
|---|
| 216 | ),
|
|---|
| 217 | UNIQUE (staff_id, appointment_date, appointment_time)
|
|---|
| 218 | );
|
|---|
| 219 |
|
|---|
| 220 | CREATE TABLE appointment_service
|
|---|
| 221 | (
|
|---|
| 222 | appointment_id INT NOT NULL REFERENCES appointment (appointment_id) ON DELETE SET DEFAULT,
|
|---|
| 223 | service_id INT NOT NULL REFERENCES service (service_id),
|
|---|
| 224 | duration_minutes SMALLINT NOT NULL CHECK (duration_minutes > 0),
|
|---|
| 225 | price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
|
|---|
| 226 | PRIMARY KEY (appointment_id, service_id)
|
|---|
| 227 | );
|
|---|
| 228 |
|
|---|
| 229 |
|
|---|
| 230 |
|
|---|
| 231 | CREATE TABLE product
|
|---|
| 232 | (
|
|---|
| 233 | product_id SERIAL PRIMARY KEY,
|
|---|
| 234 | product_name VARCHAR(150) NOT NULL,
|
|---|
| 235 | brand VARCHAR(100),
|
|---|
| 236 | unit_price NUMERIC(10, 2) NOT NULL CHECK (unit_price >= 0),
|
|---|
| 237 | reorder_level INT NOT NULL DEFAULT 0 CHECK (reorder_level >= 0)
|
|---|
| 238 | );
|
|---|
| 239 |
|
|---|
| 240 | CREATE TABLE appointment_product
|
|---|
| 241 | (
|
|---|
| 242 | appointment_id INT NOT NULL REFERENCES appointment (appointment_id) ON DELETE CASCADE,
|
|---|
| 243 | product_id INT NOT NULL REFERENCES product (product_id),
|
|---|
| 244 | quantity_used NUMERIC(10, 3) NOT NULL CHECK (quantity_used > 0),
|
|---|
| 245 | PRIMARY KEY (appointment_id, product_id)
|
|---|
| 246 | );
|
|---|
| 247 |
|
|---|
| 248 | CREATE TABLE inventory
|
|---|
| 249 | (
|
|---|
| 250 | inventory_id SERIAL PRIMARY KEY,
|
|---|
| 251 | product_id INT NOT NULL REFERENCES product (product_id),
|
|---|
| 252 | location_id INT NOT NULL REFERENCES company_location (location_id),
|
|---|
| 253 | quantity_on_hand NUMERIC(10, 3) NOT NULL DEFAULT 0 CHECK (quantity_on_hand >= 0),
|
|---|
| 254 | UNIQUE (product_id, location_id)
|
|---|
| 255 | );
|
|---|
| 256 |
|
|---|
| 257 | CREATE TABLE invoice
|
|---|
| 258 | (
|
|---|
| 259 | invoice_id SERIAL PRIMARY KEY,
|
|---|
| 260 | appointment_id INT NOT NULL UNIQUE REFERENCES appointment (appointment_id) ON DELETE RESTRICT,
|
|---|
| 261 | client_id INT NOT NULL REFERENCES client (client_id),
|
|---|
| 262 | invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|---|
| 263 | subtotal NUMERIC(10, 2) NOT NULL CHECK (subtotal >= 0),
|
|---|
| 264 | discount_total NUMERIC(10, 2) NOT NULL DEFAULT 0 CHECK (discount_total >= 0),
|
|---|
| 265 | tax NUMERIC(10, 2) NOT NULL DEFAULT 0 CHECK (tax >= 0),
|
|---|
| 266 | total NUMERIC(10, 2) GENERATED ALWAYS AS (subtotal + tax - discount_total) STORED,
|
|---|
| 267 | payment_method payment_method_enum NOT NULL
|
|---|
| 268 | );
|
|---|
| 269 |
|
|---|
| 270 | CREATE TABLE promo_code
|
|---|
| 271 | (
|
|---|
| 272 | promo_id SERIAL PRIMARY KEY,
|
|---|
| 273 | company_id INT NOT NULL REFERENCES company (company_id) ON DELETE CASCADE,
|
|---|
| 274 | code VARCHAR(50) NOT NULL,
|
|---|
| 275 | discount_type discount_type_enum NOT NULL,
|
|---|
| 276 | discount_value NUMERIC(10, 2) NOT NULL CHECK (discount_value > 0),
|
|---|
| 277 | valid_from DATE NOT NULL,
|
|---|
| 278 | valid_until DATE NOT NULL,
|
|---|
| 279 | CONSTRAINT chk_promo_dates CHECK (valid_until >= valid_from),
|
|---|
| 280 | UNIQUE (company_id, code)
|
|---|
| 281 | );
|
|---|
| 282 |
|
|---|
| 283 | CREATE TABLE invoice_promo
|
|---|
| 284 | (
|
|---|
| 285 | invoice_id INT NOT NULL REFERENCES invoice (invoice_id) ON DELETE CASCADE,
|
|---|
| 286 | promo_id INT NOT NULL REFERENCES promo_code (promo_id) ON DELETE CASCADE,
|
|---|
| 287 | PRIMARY KEY (invoice_id, promo_id)
|
|---|
| 288 | );
|
|---|
| 289 |
|
|---|
| 290 |
|
|---|
| 291 |
|
|---|
| 292 | CREATE TABLE review
|
|---|
| 293 | (
|
|---|
| 294 | review_id SERIAL PRIMARY KEY,
|
|---|
| 295 | appointment_id INT NOT NULL UNIQUE REFERENCES appointment (appointment_id),
|
|---|
| 296 | client_id INT NOT NULL REFERENCES client (client_id),
|
|---|
| 297 | rating SMALLINT NOT NULL CHECK (rating BETWEEN 1 AND 5),
|
|---|
| 298 | comment TEXT,
|
|---|
| 299 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|---|
| 300 | );
|
|---|
| 301 |
|
|---|
| 302 | CREATE TABLE loyalty_transaction
|
|---|
| 303 | (
|
|---|
| 304 | transaction_id SERIAL PRIMARY KEY,
|
|---|
| 305 | client_id INT NOT NULL REFERENCES client (client_id),
|
|---|
| 306 | appointment_id INT REFERENCES appointment (appointment_id) ON DELETE SET NULL,
|
|---|
| 307 | points_earned INT NOT NULL DEFAULT 0 CHECK (points_earned >= 0),
|
|---|
| 308 | points_spent INT NOT NULL DEFAULT 0 CHECK (points_spent >= 0),
|
|---|
| 309 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|---|
| 310 | CONSTRAINT chk_loyalty_nonzero CHECK (points_earned > 0 OR points_spent > 0)
|
|---|
| 311 | );
|
|---|
| 312 |
|
|---|
| 313 |
|
|---|
| 314 |
|
|---|
| 315 | CREATE TABLE service_price_history
|
|---|
| 316 | (
|
|---|
| 317 | price_history_id SERIAL PRIMARY KEY,
|
|---|
| 318 | service_id INT NOT NULL REFERENCES service (service_id) ON DELETE CASCADE,
|
|---|
| 319 | old_price NUMERIC(10, 2) NOT NULL CHECK (old_price >= 0),
|
|---|
| 320 | new_price NUMERIC(10, 2) NOT NULL CHECK (new_price >= 0),
|
|---|
| 321 | changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|---|
| 322 | );
|
|---|
| 323 |
|
|---|
| 324 | CREATE TABLE staff_time_slot
|
|---|
| 325 | (
|
|---|
| 326 | slot_id SERIAL PRIMARY KEY,
|
|---|
| 327 | staff_id INT NOT NULL REFERENCES staff (staff_id) ON DELETE CASCADE,
|
|---|
| 328 | slot_start TIMESTAMP NOT NULL,
|
|---|
| 329 | slot_end TIMESTAMP GENERATED ALWAYS AS (slot_start + INTERVAL '15 minutes') STORED,
|
|---|
| 330 | appointment_id INT REFERENCES appointment (appointment_id) ON DELETE SET NULL,
|
|---|
| 331 | CONSTRAINT chk_slot_order CHECK (slot_end > slot_start),
|
|---|
| 332 | UNIQUE (staff_id, slot_start)
|
|---|
| 333 | ); |
|---|