ข้ามไปที่เนื้อหา

Database Schema - ระบบงาน Admission ผู้ป่วยใน (Inpatient Admission System)

Document Version: 1.0
Date: 28 สิงหาคม 2568
Based on: SRS Inpatient Admission System และ Master Database Schema
System Integration: EMR Core, OPD-CPOE, ER System, IPD-CPOE, DRG System


📋 Overview

ระบบ Admission ผู้ป่วยใน เป็นระบบหลักในการจัดการการรับผู้ป่วยเข้าโรงพยาบาล การจัดการเตียง และการประสานงานระหว่างระบบต่าง ๆ โดยเชื่อมต่อข้อมูลจากระบบ OPD, ER และส่งต่อข้อมูลไปยัง IPD-CPOE และ DRG System

🔗 System Dependencies

graph LR
    A[EMR Core] --> E[Admission System]
    B[OPD-CPOE] --> E
    C[ER System] --> E
    D[Appointment] --> E
    E --> F[IPD-CPOE]
    E --> G[DRG System]
    E --> H[การเงินผู้ป่วยใน]
    E --> I[พยาบาลผู้ป่วยใน]

🗃️ Core Admission Tables

1. Patient Admission Management

1.1 admissions - การรับผู้ป่วยใน

CREATE TABLE admissions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- Admission Identifiers (เชื่อมกับ Master Schema)
    an VARCHAR(20) UNIQUE NOT NULL, -- Admission Number
    hn VARCHAR(20) NOT NULL, -- Hospital Number  
    patient_id UUID NOT NULL REFERENCES patients(id),
    visit_id UUID REFERENCES medical_visits(id),

    -- Source Information
    admission_source VARCHAR(50) NOT NULL CHECK (
        admission_source IN ('OPD', 'ER', 'transfer_in', 'direct', 'appointment', 'readmission')
    ),
    source_visit_id UUID, -- อ้างอิงไป OPD Visit หรือ ER Case
    source_department_id UUID REFERENCES departments(id),
    referring_doctor_id UUID REFERENCES users(id),

    -- Admission Details
    admission_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    admission_time TIME NOT NULL DEFAULT CURRENT_TIME,
    admission_type VARCHAR(20) NOT NULL DEFAULT 'elective' CHECK (
        admission_type IN ('emergency', 'elective', 'observation', 'same_day', 'newborn', 'maternity')
    ),

    -- Medical Information
    chief_complaint TEXT,
    primary_diagnosis VARCHAR(200),
    primary_icd10 VARCHAR(8), -- เชื่อมกับ ICD-10 codes
    secondary_diagnoses JSONB DEFAULT '[]', -- Array of ICD-10 codes
    provisional_diagnosis VARCHAR(200),

    -- Physician Assignment
    admitting_doctor_id UUID NOT NULL REFERENCES users(id),
    attending_doctor_id UUID REFERENCES users(id),
    consulting_doctors UUID[] DEFAULT '{}', -- Array of doctor IDs

    -- Current Location
    current_ward_id UUID REFERENCES wards(id),
    current_bed_id UUID REFERENCES beds(id),
    current_room VARCHAR(20),

    -- Patient Classification
    acuity_level INTEGER DEFAULT 1 CHECK (acuity_level BETWEEN 1 AND 5),
    isolation_required BOOLEAN DEFAULT FALSE,
    isolation_type VARCHAR(30), -- 'contact', 'droplet', 'airborne', 'protective'
    special_needs TEXT,

    -- Insurance & Financial
    primary_insurance_type VARCHAR(50),
    primary_insurance_number VARCHAR(100),
    secondary_insurance_type VARCHAR(50),
    secondary_insurance_number VARCHAR(100),

    -- Admission Status
    status VARCHAR(20) DEFAULT 'active' CHECK (
        status IN ('pending', 'active', 'discharged', 'transferred_out', 'expired', 'cancelled')
    ),

    -- Discharge Information
    discharge_date TIMESTAMP WITH TIME ZONE,
    discharge_time TIME,
    discharge_type VARCHAR(30) CHECK (
        discharge_type IN ('home', 'home_health', 'snf', 'rehab', 'ltac', 'hospice', 'ama', 'lama', 'transfer', 'expired')
    ),
    discharge_diagnosis VARCHAR(200),
    discharge_doctor_id UUID REFERENCES users(id),

    -- Length of Stay
    los_days INTEGER GENERATED ALWAYS AS (
        CASE 
            WHEN discharge_date IS NOT NULL 
            THEN EXTRACT(days FROM (discharge_date - admission_date))::INTEGER
            ELSE EXTRACT(days FROM (CURRENT_TIMESTAMP - admission_date))::INTEGER
        END
    ) STORED,

    -- Expected Discharge
    expected_discharge_date DATE,
    discharge_planning_notes TEXT,

    -- Quality Indicators
    readmission_within_30_days BOOLEAN DEFAULT FALSE,
    previous_admission_id UUID REFERENCES admissions(id),

    -- Special Flags
    is_observation BOOLEAN DEFAULT FALSE,
    is_same_day_surgery BOOLEAN DEFAULT FALSE,
    is_newborn BOOLEAN DEFAULT FALSE,
    is_emergency BOOLEAN DEFAULT FALSE,

    -- Pre-admission Information
    pre_admission_testing_complete BOOLEAN DEFAULT FALSE,
    pre_admission_notes TEXT,

    -- Audit Trail
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES users(id),
    updated_by UUID REFERENCES users(id),

    -- Constraints
    CONSTRAINT valid_admission_dates CHECK (
        discharge_date IS NULL OR discharge_date >= admission_date
    ),
    CONSTRAINT valid_insurance CHECK (
        primary_insurance_type IS NULL OR primary_insurance_number IS NOT NULL
    )
);

-- Indexes for admissions
CREATE UNIQUE INDEX idx_admissions_an ON admissions(an);
CREATE INDEX idx_admissions_hn ON admissions(hn);
CREATE INDEX idx_admissions_patient_id ON admissions(patient_id);
CREATE INDEX idx_admissions_visit_id ON admissions(visit_id) WHERE visit_id IS NOT NULL;
CREATE INDEX idx_admissions_admission_date ON admissions(admission_date);
CREATE INDEX idx_admissions_source ON admissions(admission_source);
CREATE INDEX idx_admissions_status ON admissions(status);
CREATE INDEX idx_admissions_current_ward ON admissions(current_ward_id) WHERE current_ward_id IS NOT NULL;
CREATE INDEX idx_admissions_current_bed ON admissions(current_bed_id) WHERE current_bed_id IS NOT NULL;
CREATE INDEX idx_admissions_doctor ON admissions(admitting_doctor_id);
CREATE INDEX idx_admissions_los ON admissions(los_days);
CREATE INDEX idx_admissions_active ON admissions(status, admission_date) 
    WHERE status = 'active';

-- GIN index for JSON columns
CREATE INDEX idx_admissions_secondary_diagnoses ON admissions USING gin(secondary_diagnoses);

คำอธิบายฟิลด์ (Field Descriptions):

Field Type Constraints คำอธิบาย
id UUID PRIMARY KEY รหัสเฉพาะของข้อมูลการรับผู้ป่วยใน
an VARCHAR(20) UNIQUE, NOT NULL หมายเลขการรับผู้ป่วยใน (Admission Number)
hn VARCHAR(20) NOT NULL หมายเลขผู้ป่วย (Hospital Number)
patient_id UUID NOT NULL, FK รหัสผู้ป่วย (อ้างอิงจากตาราง patients)
visit_id UUID FK รหัส Visit ที่เชื่อมโยงการมารับบริการ
admission_source VARCHAR(50) NOT NULL, CHECK แหล่งที่มาของการรับผู้ป่วยใน (OPD, ER, transfer_in, direct, appointment, readmission)
source_visit_id UUID รหัส Visit ต้นทางจาก OPD หรือ ER
source_department_id UUID FK แผนกที่ส่งตัวมา
referring_doctor_id UUID FK แพทย์ที่ส่งตัว
admission_date TIMESTAMP NOT NULL วันเวลาที่รับผู้ป่วยเข้า
admission_time TIME NOT NULL เวลาที่รับผู้ป่วยเข้า
admission_type VARCHAR(20) NOT NULL, CHECK ประเภทการรับผู้ป่วยใน (emergency, elective, observation, same_day, newborn, maternity)
chief_complaint TEXT อาการสำคัญที่มาโรงพยาบาล
primary_diagnosis VARCHAR(200) การวินิจฉัยหลัก
primary_icd10 VARCHAR(8) รหัส ICD-10 ของการวินิจฉัยหลัก
secondary_diagnoses JSONB การวินิจฉัยรอง (รายการรหัส ICD-10)
provisional_diagnosis VARCHAR(200) การวินิจฉัยเบื้องต้น
admitting_doctor_id UUID NOT NULL, FK แพทย์ที่รับผู้ป่วยเข้า
attending_doctor_id UUID FK แพทย์เจ้าของไข้
consulting_doctors UUID[] รายการแพทย์ที่ปรึกษา
current_ward_id UUID FK หอผู้ป่วยปัจจุบัน
current_bed_id UUID FK เตียงปัจจุบัน
current_room VARCHAR(20) ห้องปัจจุบัน
acuity_level INTEGER CHECK ระดับความรุนแรงของอาการ (1-5)
isolation_required BOOLEAN ต้องการแยกกักตัวหรือไม่
isolation_type VARCHAR(30) ประเภทการแยกกักตัว (contact, droplet, airborne, protective)
special_needs TEXT ความต้องการพิเศษ
primary_insurance_type VARCHAR(50) ประเภทสิทธิการรักษาหลัก
primary_insurance_number VARCHAR(100) หมายเลขสิทธิการรักษาหลัก
secondary_insurance_type VARCHAR(50) ประเภทสิทธิการรักษารอง
secondary_insurance_number VARCHAR(100) หมายเลขสิทธิการรักษารอง
status VARCHAR(20) CHECK สถานะการรับผู้ป่วยใน (pending, active, discharged, transferred_out, expired, cancelled)
discharge_date TIMESTAMP วันเวลาที่จำหน่าย
discharge_time TIME เวลาที่จำหน่าย
discharge_type VARCHAR(30) CHECK ประเภทการจำหน่าย (home, home_health, snf, rehab, ltac, hospice, ama, lama, transfer, expired)
discharge_diagnosis VARCHAR(200) การวินิจฉัยขณะจำหน่าย
discharge_doctor_id UUID FK แพทย์ที่จำหน่าย
los_days INTEGER GENERATED จำนวนวันนอนโรงพยาบาล (คำนวณอัตโนมัติ)
expected_discharge_date DATE วันที่คาดว่าจะจำหน่าย
discharge_planning_notes TEXT หมายเหตุการวางแผนจำหน่าย
readmission_within_30_days BOOLEAN กลับมารักษาซ้ำภายใน 30 วันหรือไม่
previous_admission_id UUID FK การรับผู้ป่วยครั้งก่อน (ถ้ามี)
is_observation BOOLEAN เป็นการรักษาแบบสังเกตอาการหรือไม่
is_same_day_surgery BOOLEAN เป็นผ่าตัดไปกลับในวันเดียวหรือไม่
is_newborn BOOLEAN เป็นทารกแรกเกิดหรือไม่
is_emergency BOOLEAN เป็นกรณีฉุกเฉินหรือไม่
pre_admission_testing_complete BOOLEAN ตรวจสอบก่อนรับผู้ป่วยเสร็จแล้วหรือไม่
pre_admission_notes TEXT หมายเหตุก่อนรับผู้ป่วย
created_at TIMESTAMP DEFAULT NOW() วันเวลาที่สร้างข้อมูล
updated_at TIMESTAMP DEFAULT NOW() วันเวลาที่แก้ไขข้อมูลล่าสุด
created_by UUID FK ผู้สร้างข้อมูล
updated_by UUID FK ผู้แก้ไขข้อมูลล่าสุด

1.2 admission_transfers - ประวัติการย้ายเตียง

CREATE TABLE admission_transfers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    admission_id UUID NOT NULL REFERENCES admissions(id) ON DELETE CASCADE,
    an VARCHAR(20) NOT NULL,

    -- Transfer Details
    transfer_type VARCHAR(30) NOT NULL CHECK (
        transfer_type IN ('bed_change', 'room_change', 'ward_change', 'unit_change', 'level_of_care')
    ),

    -- From Location
    from_ward_id UUID REFERENCES wards(id),
    from_bed_id UUID REFERENCES beds(id),
    from_room VARCHAR(20),
    from_unit VARCHAR(50),

    -- To Location
    to_ward_id UUID REFERENCES wards(id),
    to_bed_id UUID REFERENCES beds(id),
    to_room VARCHAR(20),
    to_unit VARCHAR(50),

    -- Transfer Information
    transfer_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    transfer_reason VARCHAR(100),
    transfer_reason_detail TEXT,
    medical_necessity BOOLEAN DEFAULT FALSE,

    -- Authorization
    ordered_by UUID REFERENCES users(id),
    approved_by UUID REFERENCES users(id),
    executed_by UUID NOT NULL REFERENCES users(id),

    -- Status
    transfer_status VARCHAR(20) DEFAULT 'completed' CHECK (
        transfer_status IN ('pending', 'in_progress', 'completed', 'cancelled')
    ),

    -- Notes
    transfer_notes TEXT,
    patient_condition_notes TEXT,

    -- Audit
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES users(id),

    CONSTRAINT transfer_location_change CHECK (
        (from_ward_id != to_ward_id) OR 
        (from_bed_id != to_bed_id) OR 
        (from_room != to_room)
    )
);

-- Indexes for transfers
CREATE INDEX idx_admission_transfers_admission_id ON admission_transfers(admission_id);
CREATE INDEX idx_admission_transfers_an ON admission_transfers(an);
CREATE INDEX idx_admission_transfers_date ON admission_transfers(transfer_date);
CREATE INDEX idx_admission_transfers_from_bed ON admission_transfers(from_bed_id);
CREATE INDEX idx_admission_transfers_to_bed ON admission_transfers(to_bed_id);
CREATE INDEX idx_admission_transfers_type ON admission_transfers(transfer_type);
CREATE INDEX idx_admission_transfers_status ON admission_transfers(transfer_status);

คำอธิบายฟิลด์ (Field Descriptions):

Field Type Constraints คำอธิบาย
id UUID PRIMARY KEY รหัสเฉพาะของการย้ายเตียง
admission_id UUID NOT NULL, FK รหัสการรับผู้ป่วยใน
an VARCHAR(20) NOT NULL หมายเลขการรับผู้ป่วยใน
transfer_type VARCHAR(30) NOT NULL, CHECK ประเภทการย้าย (bed_change, room_change, ward_change, unit_change, level_of_care)
from_ward_id UUID FK หอผู้ป่วยต้นทาง
from_bed_id UUID FK เตียงต้นทาง
from_room VARCHAR(20) ห้องต้นทาง
from_unit VARCHAR(50) หน่วยต้นทาง
to_ward_id UUID FK หอผู้ป่วยปลายทาง
to_bed_id UUID FK เตียงปลายทาง
to_room VARCHAR(20) ห้องปลายทาง
to_unit VARCHAR(50) หน่วยปลายทาง
transfer_date TIMESTAMP NOT NULL วันเวลาที่ย้าย
transfer_reason VARCHAR(100) เหตุผลการย้าย
transfer_reason_detail TEXT รายละเอียดเหตุผลการย้าย
medical_necessity BOOLEAN จำเป็นทางการแพทย์หรือไม่
ordered_by UUID FK ผู้สั่งการย้าย
approved_by UUID FK ผู้อนุมัติการย้าย
executed_by UUID NOT NULL, FK ผู้ดำเนินการย้าย
transfer_status VARCHAR(20) CHECK สถานะการย้าย (pending, in_progress, completed, cancelled)
transfer_notes TEXT หมายเหตุการย้าย
patient_condition_notes TEXT หมายเหตุสภาพผู้ป่วย
created_at TIMESTAMP DEFAULT NOW() วันเวลาที่สร้างข้อมูล
created_by UUID FK ผู้สร้างข้อมูล

2. Bed and Ward Management

2.1 wards - หอผู้ป่วย/แผนก

CREATE TABLE wards (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    ward_code VARCHAR(20) UNIQUE NOT NULL,
    ward_name VARCHAR(100) NOT NULL,
    ward_name_en VARCHAR(100),

    -- Ward Classification
    ward_type VARCHAR(30) NOT NULL CHECK (
        ward_type IN ('general', 'icu', 'ccu', 'nicu', 'picu', 'maternity', 'pediatric', 
                     'surgical', 'medical', 'orthopedic', 'psychiatric', 'isolation', 'emergency')
    ),
    specialty VARCHAR(50),

    -- Physical Location
    building VARCHAR(50),
    floor_number INTEGER,
    wing VARCHAR(20),

    -- Capacity
    total_beds INTEGER DEFAULT 0,
    licensed_beds INTEGER DEFAULT 0,
    staffed_beds INTEGER DEFAULT 0,

    -- Ward Characteristics
    is_icu BOOLEAN DEFAULT FALSE,
    is_step_down BOOLEAN DEFAULT FALSE,
    is_isolation_capable BOOLEAN DEFAULT FALSE,
    is_maternity BOOLEAN DEFAULT FALSE,
    is_pediatric BOOLEAN DEFAULT FALSE,
    is_psychiatric BOOLEAN DEFAULT FALSE,

    -- Staff Information
    nurse_manager_id UUID REFERENCES users(id),
    charge_nurse_id UUID REFERENCES users(id),
    unit_clerk_id UUID REFERENCES users(id),

    -- Operating Hours
    visiting_hours_start TIME DEFAULT '08:00:00',
    visiting_hours_end TIME DEFAULT '20:00:00',
    restricted_visiting BOOLEAN DEFAULT FALSE,

    -- Capabilities
    oxygen_available BOOLEAN DEFAULT TRUE,
    suction_available BOOLEAN DEFAULT TRUE,
    telemetry_available BOOLEAN DEFAULT FALSE,
    isolation_rooms INTEGER DEFAULT 0,

    -- Contact Information
    phone_number VARCHAR(20),
    extension VARCHAR(10),

    -- Status
    is_active BOOLEAN DEFAULT TRUE,
    temporarily_closed BOOLEAN DEFAULT FALSE,
    closure_reason TEXT,

    -- Audit
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES users(id),
    updated_by UUID REFERENCES users(id)
);

-- Indexes for wards
CREATE UNIQUE INDEX idx_wards_code ON wards(ward_code);
CREATE INDEX idx_wards_name ON wards(ward_name);
CREATE INDEX idx_wards_type ON wards(ward_type);
CREATE INDEX idx_wards_building_floor ON wards(building, floor_number);
CREATE INDEX idx_wards_active ON wards(is_active);
CREATE INDEX idx_wards_specialty ON wards(specialty) WHERE specialty IS NOT NULL;

คำอธิบายฟิลด์ (Field Descriptions):

Field Type Constraints คำอธิบาย
id UUID PRIMARY KEY รหัสเฉพาะของหอผู้ป่วย
ward_code VARCHAR(20) UNIQUE, NOT NULL รหัสหอผู้ป่วย
ward_name VARCHAR(100) NOT NULL ชื่อหอผู้ป่วย (ภาษาไทย)
ward_name_en VARCHAR(100) ชื่อหอผู้ป่วย (ภาษาอังกฤษ)
ward_type VARCHAR(30) NOT NULL, CHECK ประเภทหอผู้ป่วย (general, icu, ccu, nicu, picu, maternity, pediatric, surgical, medical, orthopedic, psychiatric, isolation, emergency)
specialty VARCHAR(50) ความเชี่ยวชาญ/สาขา
building VARCHAR(50) อาคาร
floor_number INTEGER ชั้น
wing VARCHAR(20) ปีก/โซน
total_beds INTEGER จำนวนเตียงทั้งหมด
licensed_beds INTEGER จำนวนเตียงที่ได้รับใบอนุญาต
staffed_beds INTEGER จำนวนเตียงที่มีพยาบาลดูแล
is_icu BOOLEAN เป็นหอผู้ป่วยวิกฤตหรือไม่
is_step_down BOOLEAN เป็นหอผู้ป่วยกึ่งวิกฤตหรือไม่
is_isolation_capable BOOLEAN สามารถแยกกักตัวได้หรือไม่
is_maternity BOOLEAN เป็นหอคลอดหรือไม่
is_pediatric BOOLEAN เป็นหอเด็กหรือไม่
is_psychiatric BOOLEAN เป็นหอจิตเวชหรือไม่
nurse_manager_id UUID FK รหัสหัวหน้าพยาบาล
charge_nurse_id UUID FK รหัสพยาบาลหัวหน้าเวร
unit_clerk_id UUID FK รหัสเสมียนหอผู้ป่วย
visiting_hours_start TIME เวลาเริ่มเยี่ยม
visiting_hours_end TIME เวลาสิ้นสุดเยี่ยม
restricted_visiting BOOLEAN จำกัดการเยี่ยมหรือไม่
oxygen_available BOOLEAN มีออกซิเจนหรือไม่
suction_available BOOLEAN มีเครื่องดูดเสมหะหรือไม่
telemetry_available BOOLEAN มีระบบตรวจสอบสัญญาณชีพหรือไม่
isolation_rooms INTEGER จำนวนห้องแยกกักตัว
phone_number VARCHAR(20) เบอร์โทรศัพท์
extension VARCHAR(10) เบอร์ภายใน
is_active BOOLEAN สถานะการใช้งาน
temporarily_closed BOOLEAN ปิดชั่วคราวหรือไม่
closure_reason TEXT เหตุผลการปิด
created_at TIMESTAMP DEFAULT NOW() วันเวลาที่สร้างข้อมูล
updated_at TIMESTAMP DEFAULT NOW() วันเวลาที่แก้ไขข้อมูลล่าสุด
created_by UUID FK ผู้สร้างข้อมูล
updated_by UUID FK ผู้แก้ไขข้อมูลล่าสุด

2.2 beds - เตียงผู้ป่วย

CREATE TABLE beds (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    bed_code VARCHAR(20) UNIQUE NOT NULL,
    bed_name VARCHAR(100) NOT NULL,

    -- Location
    ward_id UUID NOT NULL REFERENCES wards(id),
    room_number VARCHAR(20),
    bed_position VARCHAR(10), -- 'A', 'B', 'C', 'D' for multi-bed rooms

    -- Bed Classification
    bed_type VARCHAR(30) NOT NULL DEFAULT 'standard' CHECK (
        bed_type IN ('standard', 'private', 'semi_private', 'icu', 'ccu', 'isolation', 
                    'bariatric', 'pediatric', 'maternity', 'psychiatric', 'emergency')
    ),
    bed_category VARCHAR(20) DEFAULT 'general' CHECK (
        bed_category IN ('general', 'critical', 'intermediate', 'observation', 'specialty')
    ),

    -- Current Status
    status VARCHAR(20) DEFAULT 'available' CHECK (
        status IN ('available', 'occupied', 'reserved', 'blocked', 'maintenance', 
                  'housekeeping', 'out_of_order', 'isolation_pending')
    ),

    -- Current Patient Information
    current_patient_id UUID REFERENCES patients(id),
    current_admission_id UUID REFERENCES admissions(id),
    current_an VARCHAR(20),
    occupied_since TIMESTAMP WITH TIME ZONE,
    expected_discharge TIMESTAMP WITH TIME ZONE,

    -- Physical Characteristics
    is_private_room BOOLEAN DEFAULT FALSE,
    has_bathroom BOOLEAN DEFAULT FALSE,
    has_window BOOLEAN DEFAULT FALSE,
    room_size_sqm DECIMAL(6,2),

    -- Medical Equipment
    has_oxygen BOOLEAN DEFAULT FALSE,
    has_suction BOOLEAN DEFAULT FALSE,
    has_compressed_air BOOLEAN DEFAULT FALSE,
    has_cardiac_monitor BOOLEAN DEFAULT FALSE,
    has_ventilator BOOLEAN DEFAULT FALSE,
    has_dialysis_access BOOLEAN DEFAULT FALSE,

    -- Special Features
    is_isolation BOOLEAN DEFAULT FALSE,
    isolation_type VARCHAR(30), -- 'negative_pressure', 'positive_pressure', 'contact', 'droplet'
    is_bariatric BOOLEAN DEFAULT FALSE,
    weight_limit_kg INTEGER DEFAULT 150,

    -- Accessibility
    is_wheelchair_accessible BOOLEAN DEFAULT TRUE,
    has_hoyer_lift BOOLEAN DEFAULT FALSE,

    -- Pricing Information
    base_daily_rate DECIMAL(10,2) DEFAULT 0.00,
    private_room_rate DECIMAL(10,2) DEFAULT 0.00,
    icu_rate DECIMAL(10,2) DEFAULT 0.00,

    -- Maintenance Information
    last_maintenance_date DATE,
    next_maintenance_date DATE,
    maintenance_notes TEXT,

    -- Housekeeping
    last_cleaned TIMESTAMP WITH TIME ZONE,
    cleaning_status VARCHAR(20) DEFAULT 'clean' CHECK (
        cleaning_status IN ('clean', 'dirty', 'cleaning_in_progress', 'terminal_clean_required')
    ),

    -- Reservation Information
    reserved_for_patient_id UUID REFERENCES patients(id),
    reserved_until TIMESTAMP WITH TIME ZONE,
    reservation_notes TEXT,

    -- Blocking Information
    blocked_reason VARCHAR(100),
    blocked_until TIMESTAMP WITH TIME ZONE,
    blocked_by UUID REFERENCES users(id),

    -- Status History
    status_changed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    status_changed_by UUID REFERENCES users(id),

    -- Bed Management
    is_temporary BOOLEAN DEFAULT FALSE,
    is_emergency_bed BOOLEAN DEFAULT FALSE,
    is_surge_capacity BOOLEAN DEFAULT FALSE,

    -- Active Status
    is_active BOOLEAN DEFAULT TRUE,
    deactivated_date TIMESTAMP WITH TIME ZONE,
    deactivation_reason TEXT,

    -- Audit
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES users(id),
    updated_by UUID REFERENCES users(id),

    -- Constraints
    CONSTRAINT bed_current_patient_consistent CHECK (
        (current_patient_id IS NULL AND current_admission_id IS NULL AND current_an IS NULL) OR
        (current_patient_id IS NOT NULL AND current_admission_id IS NOT NULL AND current_an IS NOT NULL)
    ),
    CONSTRAINT bed_reservation_consistent CHECK (
        (reserved_for_patient_id IS NULL AND reserved_until IS NULL) OR
        (reserved_for_patient_id IS NOT NULL AND reserved_until IS NOT NULL AND reserved_until > CURRENT_TIMESTAMP)
    )
);

-- Indexes for beds
CREATE UNIQUE INDEX idx_beds_code ON beds(bed_code);
CREATE INDEX idx_beds_ward_id ON beds(ward_id);
CREATE INDEX idx_beds_status ON beds(status);
CREATE INDEX idx_beds_type ON beds(bed_type);
CREATE INDEX idx_beds_available ON beds(status, ward_id) WHERE status = 'available';
CREATE INDEX idx_beds_occupied ON beds(status, current_patient_id) WHERE status = 'occupied';
CREATE INDEX idx_beds_room ON beds(ward_id, room_number);
CREATE INDEX idx_beds_current_patient ON beds(current_patient_id) WHERE current_patient_id IS NOT NULL;
CREATE INDEX idx_beds_current_admission ON beds(current_admission_id) WHERE current_admission_id IS NOT NULL;
CREATE INDEX idx_beds_reserved ON beds(reserved_for_patient_id, reserved_until) 
    WHERE reserved_for_patient_id IS NOT NULL;
CREATE INDEX idx_beds_active ON beds(is_active, ward_id);
CREATE INDEX idx_beds_pricing ON beds(bed_type, base_daily_rate);

คำอธิบายฟิลด์ (Field Descriptions):

Field Type Constraints คำอธิบาย
id UUID PRIMARY KEY รหัสเฉพาะของเตียง
bed_code VARCHAR(20) UNIQUE, NOT NULL รหัสเตียง
bed_name VARCHAR(100) NOT NULL ชื่อเตียง
ward_id UUID NOT NULL, FK รหัสหอผู้ป่วย
room_number VARCHAR(20) หมายเลขห้อง
bed_position VARCHAR(10) ตำแหน่งเตียงในห้อง (A, B, C, D)
bed_type VARCHAR(30) NOT NULL, CHECK ประเภทเตียง (standard, private, semi_private, icu, ccu, isolation, bariatric, pediatric, maternity, psychiatric, emergency)
bed_category VARCHAR(20) CHECK หมวดหมู่เตียง (general, critical, intermediate, observation, specialty)
status VARCHAR(20) CHECK สถานะเตียง (available, occupied, reserved, blocked, maintenance, housekeeping, out_of_order, isolation_pending)
current_patient_id UUID FK ผู้ป่วยปัจจุบัน
current_admission_id UUID FK การรับผู้ป่วยปัจจุบัน
current_an VARCHAR(20) หมายเลข AN ปัจจุบัน
occupied_since TIMESTAMP เวลาที่เริ่มใช้เตียง
expected_discharge TIMESTAMP เวลาที่คาดว่าจะจำหน่าย
is_private_room BOOLEAN เป็นห้องส่วนตัวหรือไม่
has_bathroom BOOLEAN มีห้องน้ำในตัวหรือไม่
has_window BOOLEAN มีหน้าต่างหรือไม่
room_size_sqm DECIMAL(6,2) ขนาดห้องตารางเมตร
has_oxygen BOOLEAN มีออกซิเจนหรือไม่
has_suction BOOLEAN มีเครื่องดูดเสมหะหรือไม่
has_compressed_air BOOLEAN มีอากาศอัดหรือไม่
has_cardiac_monitor BOOLEAN มีเครื่องตรวจสอบหัวใจหรือไม่
has_ventilator BOOLEAN มีเครื่องช่วยหายใจหรือไม่
has_dialysis_access BOOLEAN มีช่องทางล้างไตหรือไม่
is_isolation BOOLEAN เป็นห้องแยกกักตัวหรือไม่
isolation_type VARCHAR(30) ประเภทการแยกกักตัว (negative_pressure, positive_pressure, contact, droplet)
is_bariatric BOOLEAN เป็นเตียงสำหรับผู้ป่วยอ้วนหรือไม่
weight_limit_kg INTEGER ขีดจำกัดน้ำหนัก (กิโลกรัม)
is_wheelchair_accessible BOOLEAN เข้าถึงได้ด้วยรถเข็นหรือไม่
has_hoyer_lift BOOLEAN มีเครื่องยกผู้ป่วยหรือไม่
base_daily_rate DECIMAL(10,2) อัตราค่าบริการต่อวัน (พื้นฐาน)
private_room_rate DECIMAL(10,2) อัตราค่าบริการห้องส่วนตัว
icu_rate DECIMAL(10,2) อัตราค่าบริการ ICU
last_maintenance_date DATE วันที่ซ่อมบำรุงล่าสุด
next_maintenance_date DATE วันที่ซ่อมบำรุงครั้งต่อไป
maintenance_notes TEXT หมายเหตุการซ่อมบำรุง
last_cleaned TIMESTAMP เวลาที่ทำความสะอาดล่าสุด
cleaning_status VARCHAR(20) CHECK สถานะความสะอาด (clean, dirty, cleaning_in_progress, terminal_clean_required)
reserved_for_patient_id UUID FK ผู้ป่วยที่จองเตียงไว้
reserved_until TIMESTAMP จองไว้จนถึงเวลา
reservation_notes TEXT หมายเหตุการจอง
blocked_reason VARCHAR(100) เหตุผลการปิดกั้น
blocked_until TIMESTAMP ปิดกั้นจนถึงเวลา
blocked_by UUID FK ผู้ที่ปิดกั้น
status_changed_at TIMESTAMP เวลาที่เปลี่ยนสถานะ
status_changed_by UUID FK ผู้เปลี่ยนสถานะ
is_temporary BOOLEAN เป็นเตียงชั่วคราวหรือไม่
is_emergency_bed BOOLEAN เป็นเตียงฉุกเฉินหรือไม่
is_surge_capacity BOOLEAN เป็นเตียงเพิ่มกำลังผลิตหรือไม่
is_active BOOLEAN สถานะการใช้งาน
deactivated_date TIMESTAMP วันที่ปิดการใช้งาน
deactivation_reason TEXT เหตุผลการปิดใช้งาน
created_at TIMESTAMP DEFAULT NOW() วันเวลาที่สร้างข้อมูล
updated_at TIMESTAMP DEFAULT NOW() วันเวลาที่แก้ไขข้อมูลล่าสุด
created_by UUID FK ผู้สร้างข้อมูล
updated_by UUID FK ผู้แก้ไขข้อมูลล่าสุด

2.3 bed_reservations - การจองเตียง

CREATE TABLE bed_reservations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- Reservation Details
    reservation_code VARCHAR(20) UNIQUE NOT NULL,
    patient_id UUID NOT NULL REFERENCES patients(id),
    hn VARCHAR(20) NOT NULL,

    -- Bed Information
    bed_id UUID REFERENCES beds(id),
    ward_id UUID NOT NULL REFERENCES wards(id),
    preferred_bed_type VARCHAR(30),

    -- Timing
    reservation_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    requested_admission_date TIMESTAMP WITH TIME ZONE NOT NULL,
    requested_admission_time TIME,
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,

    -- Medical Information
    diagnosis VARCHAR(200),
    icd10_code VARCHAR(8),
    expected_los INTEGER,
    acuity_level INTEGER DEFAULT 1 CHECK (acuity_level BETWEEN 1 AND 5),
    special_requirements TEXT,

    -- Source Information
    reservation_source VARCHAR(50) NOT NULL CHECK (
        reservation_source IN ('appointment', 'opd', 'er', 'transfer', 'surgery', 'procedure', 'direct')
    ),
    source_visit_id UUID,
    source_appointment_id UUID,

    -- Physician Information
    requesting_doctor_id UUID NOT NULL REFERENCES users(id),
    admitting_doctor_id UUID REFERENCES users(id),

    -- Status
    status VARCHAR(20) DEFAULT 'active' CHECK (
        status IN ('active', 'confirmed', 'admitted', 'cancelled', 'expired', 'no_show')
    ),

    -- Confirmation
    confirmed_at TIMESTAMP WITH TIME ZONE,
    confirmed_by UUID REFERENCES users(id),
    confirmation_notes TEXT,

    -- Cancellation
    cancelled_at TIMESTAMP WITH TIME ZONE,
    cancelled_by UUID REFERENCES users(id),
    cancellation_reason VARCHAR(100),
    cancellation_notes TEXT,

    -- Admission Link
    admission_id UUID REFERENCES admissions(id),
    admitted_at TIMESTAMP WITH TIME ZONE,

    -- Priority
    priority INTEGER DEFAULT 3 CHECK (priority BETWEEN 1 AND 5), -- 1=highest, 5=lowest
    is_urgent BOOLEAN DEFAULT FALSE,

    -- Notification
    patient_notified BOOLEAN DEFAULT FALSE,
    notification_sent_at TIMESTAMP WITH TIME ZONE,
    reminder_sent BOOLEAN DEFAULT FALSE,

    -- Insurance Pre-approval
    insurance_preauth_required BOOLEAN DEFAULT FALSE,
    insurance_preauth_obtained BOOLEAN DEFAULT FALSE,
    preauth_number VARCHAR(50),

    -- Notes
    reservation_notes TEXT,
    patient_instructions TEXT,

    -- Audit
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES users(id),
    updated_by UUID REFERENCES users(id),

    -- Constraints
    CONSTRAINT valid_reservation_dates CHECK (
        requested_admission_date >= CURRENT_DATE AND 
        expires_at > requested_admission_date
    ),
    CONSTRAINT reservation_admitted_link CHECK (
        (status = 'admitted' AND admission_id IS NOT NULL AND admitted_at IS NOT NULL) OR
        (status != 'admitted')
    )
);

-- Indexes for bed reservations
CREATE UNIQUE INDEX idx_bed_reservations_code ON bed_reservations(reservation_code);
CREATE INDEX idx_bed_reservations_patient ON bed_reservations(patient_id);
CREATE INDEX idx_bed_reservations_hn ON bed_reservations(hn);
CREATE INDEX idx_bed_reservations_bed ON bed_reservations(bed_id) WHERE bed_id IS NOT NULL;
CREATE INDEX idx_bed_reservations_ward ON bed_reservations(ward_id);
CREATE INDEX idx_bed_reservations_admission_date ON bed_reservations(requested_admission_date);
CREATE INDEX idx_bed_reservations_status ON bed_reservations(status);
CREATE INDEX idx_bed_reservations_active ON bed_reservations(status, requested_admission_date) 
    WHERE status IN ('active', 'confirmed');
CREATE INDEX idx_bed_reservations_expires ON bed_reservations(expires_at) WHERE status IN ('active', 'confirmed');
CREATE INDEX idx_bed_reservations_doctor ON bed_reservations(requesting_doctor_id);
CREATE INDEX idx_bed_reservations_source ON bed_reservations(reservation_source);
CREATE INDEX idx_bed_reservations_priority ON bed_reservations(priority, requested_admission_date);

คำอธิบายฟิลด์ (Field Descriptions):

Field Type Constraints คำอธิบาย
id UUID PRIMARY KEY รหัสเฉพาะของการจองเตียง
reservation_code VARCHAR(20) UNIQUE, NOT NULL รหัสการจองเตียง
patient_id UUID NOT NULL, FK รหัสผู้ป่วยที่จองเตียง
hn VARCHAR(20) NOT NULL หมายเลขผู้ป่วย
bed_id UUID FK รหัสเตียงที่จองไว้
ward_id UUID NOT NULL, FK รหัสหอผู้ป่วยที่จองไว้
preferred_bed_type VARCHAR(30) ประเภทเตียงที่ต้องการ
reservation_date TIMESTAMP NOT NULL วันเวลาที่จอง
requested_admission_date TIMESTAMP NOT NULL วันเวลาที่ขอเข้ารับผู้ป่วย
requested_admission_time TIME เวลาที่ขอเข้ารับผู้ป่วย
expires_at TIMESTAMP NOT NULL วันหมดอายุการจอง
diagnosis VARCHAR(200) การวินิจฉัย
icd10_code VARCHAR(8) รหัส ICD-10
expected_los INTEGER จำนวนวันที่คาดว่าจะนอนโรงพยาบาล
acuity_level INTEGER CHECK ระดับความรุนแรง (1-5)
special_requirements TEXT ความต้องการพิเศษ
reservation_source VARCHAR(50) NOT NULL, CHECK แหล่งที่มาการจอง (appointment, opd, er, transfer, surgery, procedure, direct)
source_visit_id UUID รหัส Visit ต้นทาง
source_appointment_id UUID รหัสนัดหมายต้นทาง
requesting_doctor_id UUID NOT NULL, FK แพทย์ที่ขอจองเตียง
admitting_doctor_id UUID FK แพทย์ที่จะรับผู้ป่วยเข้า
status VARCHAR(20) CHECK สถานะการจอง (active, confirmed, admitted, cancelled, expired, no_show)
confirmed_at TIMESTAMP เวลาที่ยืนยันการจอง
confirmed_by UUID FK ผู้ยืนยันการจอง
confirmation_notes TEXT หมายเหตุการยืนยัน
cancelled_at TIMESTAMP เวลาที่ยกเลิกการจอง
cancelled_by UUID FK ผู้ยกเลิกการจอง
cancellation_reason VARCHAR(100) เหตุผลการยกเลิก
cancellation_notes TEXT หมายเหตุการยกเลิก
admission_id UUID FK รหัสการรับผู้ป่วยจริง
admitted_at TIMESTAMP เวลาที่รับผู้ป่วยจริง
priority INTEGER CHECK ลำดับความสำคัญ (1=สูงสุด, 5=ต่ำสุด)
is_urgent BOOLEAN เป็นกรณีด่วนหรือไม่
patient_notified BOOLEAN แจ้งผู้ป่วยแล้วหรือไม่
notification_sent_at TIMESTAMP เวลาที่ส่งการแจ้งเตือน
reminder_sent BOOLEAN ส่งการแจ้งเตือนเกิดแล้วหรือไม่
insurance_preauth_required BOOLEAN ต้องการการอนุมัติสิทธิ์ล่วงหน้าหรือไม่
insurance_preauth_obtained BOOLEAN ได้รับการอนุมัติสิทธิ์แล้วหรือไม่
preauth_number VARCHAR(50) หมายเลขอนุมัติล่วงหน้า
reservation_notes TEXT หมายเหตุการจองเตียง
patient_instructions TEXT คำแนะนำสำหรับผู้ป่วย
created_at TIMESTAMP DEFAULT NOW() วันเวลาที่สร้างข้อมูล
updated_at TIMESTAMP DEFAULT NOW() วันเวลาที่แก้ไขข้อมูลล่าสุด
created_by UUID FK ผู้สร้างข้อมูล
updated_by UUID FK ผู้แก้ไขข้อมูลล่าสุด

3. Census and Occupancy Tracking

3.1 daily_census - สถิติผู้ป่วยใน รายวัน

CREATE TABLE daily_census (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    census_date DATE NOT NULL,
    ward_id UUID NOT NULL REFERENCES wards(id),

    -- Bed Counts
    total_beds INTEGER DEFAULT 0,
    staffed_beds INTEGER DEFAULT 0,
    available_beds INTEGER DEFAULT 0,
    occupied_beds INTEGER DEFAULT 0,
    reserved_beds INTEGER DEFAULT 0,
    blocked_beds INTEGER DEFAULT 0,

    -- Patient Counts
    total_patients INTEGER DEFAULT 0,
    new_admissions INTEGER DEFAULT 0,
    discharges INTEGER DEFAULT 0,
    transfers_in INTEGER DEFAULT 0,
    transfers_out INTEGER DEFAULT 0,

    -- Occupancy Metrics
    occupancy_rate DECIMAL(5,2) DEFAULT 0.00, -- Percentage
    avg_los DECIMAL(5,2) DEFAULT 0.00,
    bed_turnover_rate DECIMAL(5,2) DEFAULT 0.00,

    -- Patient Days
    patient_days INTEGER DEFAULT 0,

    -- Acuity Levels
    acuity_level_1 INTEGER DEFAULT 0,
    acuity_level_2 INTEGER DEFAULT 0,
    acuity_level_3 INTEGER DEFAULT 0,
    acuity_level_4 INTEGER DEFAULT 0,
    acuity_level_5 INTEGER DEFAULT 0,

    -- Special Populations
    isolation_patients INTEGER DEFAULT 0,
    pediatric_patients INTEGER DEFAULT 0,
    observation_patients INTEGER DEFAULT 0,

    -- Midnight Census (standard reporting time)
    midnight_census INTEGER DEFAULT 0,

    -- Revenue
    daily_revenue DECIMAL(12,2) DEFAULT 0.00,

    -- Quality Indicators
    readmissions_30_day INTEGER DEFAULT 0,
    average_satisfaction_score DECIMAL(3,2),

    -- Calculated Fields
    calculated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,

    -- Audit
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT unique_daily_census UNIQUE (census_date, ward_id)
);

-- Indexes for daily census
CREATE INDEX idx_daily_census_date ON daily_census(census_date);
CREATE INDEX idx_daily_census_ward ON daily_census(ward_id);
CREATE INDEX idx_daily_census_occupancy ON daily_census(occupancy_rate);
CREATE INDEX idx_daily_census_date_ward ON daily_census(census_date, ward_id);

คำอธิบายฟิลด์ (Field Descriptions):

Field Type Constraints คำอธิบาย
id UUID PRIMARY KEY รหัสเฉพาะของสถิติรายวัน
census_date DATE NOT NULL วันที่ทำสถิติ
ward_id UUID NOT NULL, FK รหัสหอผู้ป่วย
total_beds INTEGER จำนวนเตียงทั้งหมด
staffed_beds INTEGER จำนวนเตียงที่มีพยาบาลดูแล
available_beds INTEGER จำนวนเตียงว่าง
occupied_beds INTEGER จำนวนเตียงที่มีผู้ป่วยใช้
reserved_beds INTEGER จำนวนเตียงที่จองไว้
blocked_beds INTEGER จำนวนเตียงที่ถูกปิดกั้น
total_patients INTEGER จำนวนผู้ป่วยทั้งหมด
new_admissions INTEGER จำนวนผู้ป่วยใหม่
discharges INTEGER จำนวนผู้ป่วยที่จำหน่าย
transfers_in INTEGER จำนวนผู้ป่วยที่ย้ายเข้ามา
transfers_out INTEGER จำนวนผู้ป่วยที่ย้ายออกไป
occupancy_rate DECIMAL(5,2) อัตราการครองเตียง (เปอร์เซ็นต์)
avg_los DECIMAL(5,2) จำนวันนอนโรงพยาบาลเฉลี่ย
bed_turnover_rate DECIMAL(5,2) อัตราการหมุนเวียนของเตียง
patient_days INTEGER จำนวันผู้ป่วย (Patient Days)
acuity_level_1 INTEGER จำนวนผู้ป่วยระดับความรุนแรง 1
acuity_level_2 INTEGER จำนวนผู้ป่วยระดับความรุนแรง 2
acuity_level_3 INTEGER จำนวนผู้ป่วยระดับความรุนแรง 3
acuity_level_4 INTEGER จำนวนผู้ป่วยระดับความรุนแรง 4
acuity_level_5 INTEGER จำนวนผู้ป่วยระดับความรุนแรง 5
isolation_patients INTEGER จำนวนผู้ป่วยที่ต้องแยกกักตัว
pediatric_patients INTEGER จำนวนผู้ป่วยเด็ก
observation_patients INTEGER จำนวนผู้ป่วยสังเกตอาการ
midnight_census INTEGER สถิติ Midnight Census (เที่ยงคืน)
daily_revenue DECIMAL(12,2) รายได้รายวัน
readmissions_30_day INTEGER จำนวนการกลับมารักษาซ้ำใน 30 วัน
average_satisfaction_score DECIMAL(3,2) คะแนนความพึงพอใจเฉลี่ย
calculated_at TIMESTAMP เวลาที่คำนวณข้อมูล
created_at TIMESTAMP DEFAULT NOW() วันเวลาที่สร้างข้อมูล
updated_at TIMESTAMP DEFAULT NOW() วันเวลาที่แก้ไขข้อมูลล่าสุด

4. Emergency and Special Situations

4.1 emergency_bed_allocations - การจัดสรรเตียงฉุกเฉิน

CREATE TABLE emergency_bed_allocations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- Emergency Details
    emergency_type VARCHAR(50) NOT NULL CHECK (
        emergency_type IN ('mass_casualty', 'disaster', 'pandemic', 'surge_capacity', 'critical_shortage')
    ),
    emergency_level INTEGER NOT NULL CHECK (emergency_level BETWEEN 1 AND 5),

    -- Activation
    activated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    activated_by UUID NOT NULL REFERENCES users(id),
    deactivated_at TIMESTAMP WITH TIME ZONE,
    deactivated_by UUID REFERENCES users(id),

    -- Affected Areas
    affected_wards UUID[] DEFAULT '{}',

    -- Bed Allocations
    emergency_beds_created INTEGER DEFAULT 0,
    regular_beds_converted INTEGER DEFAULT 0,
    total_additional_capacity INTEGER DEFAULT 0,

    -- Status
    is_active BOOLEAN DEFAULT TRUE,

    -- Documentation
    activation_reason TEXT NOT NULL,
    deactivation_reason TEXT,
    emergency_notes TEXT,

    -- Approval
    approved_by UUID REFERENCES users(id),
    approval_notes TEXT,

    -- Audit
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES users(id),
    updated_by UUID REFERENCES users(id)
);

-- Indexes for emergency allocations
CREATE INDEX idx_emergency_allocations_type ON emergency_bed_allocations(emergency_type);
CREATE INDEX idx_emergency_allocations_active ON emergency_bed_allocations(is_active, activated_at);
CREATE INDEX idx_emergency_allocations_level ON emergency_bed_allocations(emergency_level);

คำอธิบายฟิลด์ (Field Descriptions):

Field Type Constraints คำอธิบาย
id UUID PRIMARY KEY รหัสเฉพาะของการจัดสรรเตียงฉุกเฉิน
emergency_type VARCHAR(50) NOT NULL, CHECK ประเภทเหตุฉุกเฉิน (mass_casualty, disaster, pandemic, surge_capacity, critical_shortage)
emergency_level INTEGER NOT NULL, CHECK ระดับความรุนแรงของเหตุฉุกเฉิน (1-5)
activated_at TIMESTAMP NOT NULL เวลาที่เริ่มใช้งาน
activated_by UUID NOT NULL, FK ผู้เริ่มใช้งาน
deactivated_at TIMESTAMP เวลาที่หยุดการใช้งาน
deactivated_by UUID FK ผู้หยุดการใช้งาน
affected_wards UUID[] รายการหอผู้ป่วยที่ได้รับผลกระทบ
emergency_beds_created INTEGER จำนวนเตียงฉุกเฉินที่สร้างขึ้น
regular_beds_converted INTEGER จำนวนเตียงปกติที่แปลงเป็นเตียงฉุกเฉิน
total_additional_capacity INTEGER กำลังผลิตเพิ่มเติมรวม
is_active BOOLEAN สถานะการทำงาน
activation_reason TEXT NOT NULL เหตุผลการเริ่มใช้งาน
deactivation_reason TEXT เหตุผลการหยุดการทำงาน
emergency_notes TEXT หมายเหตุสถานการณ์ฉุกเฉิน
approved_by UUID FK ผู้อนุมัติ
approval_notes TEXT หมายเหตุการอนุมัติ
created_at TIMESTAMP DEFAULT NOW() วันเวลาที่สร้างข้อมูล
updated_at TIMESTAMP DEFAULT NOW() วันเวลาที่แก้ไขข้อมูลล่าสุด
created_by UUID FK ผู้สร้างข้อมูล
updated_by UUID FK ผู้แก้ไขข้อมูลล่าสุด

🔄 Integration Views and Procedures

1. Comprehensive Admission View

CREATE VIEW v_admissions_complete AS
SELECT 
    a.*,
    p.first_name, p.last_name, p.national_id, p.date_of_birth,
    w.ward_name, w.ward_type,
    b.bed_code, b.bed_name, b.room_number,
    admitting_doc.username as admitting_doctor_name,
    attending_doc.username as attending_doctor_name,
    CASE 
        WHEN a.status = 'active' THEN 
            EXTRACT(days FROM (CURRENT_TIMESTAMP - a.admission_date))::INTEGER
        ELSE a.los_days 
    END as current_los
FROM admissions a
LEFT JOIN patients p ON a.patient_id = p.id
LEFT JOIN wards w ON a.current_ward_id = w.id
LEFT JOIN beds b ON a.current_bed_id = b.id
LEFT JOIN users admitting_doc ON a.admitting_doctor_id = admitting_doc.id
LEFT JOIN users attending_doc ON a.attending_doctor_id = attending_doc.id;

2. Real-time Bed Status View

CREATE VIEW v_bed_status_realtime AS
SELECT 
    b.*,
    w.ward_name, w.ward_type, w.building, w.floor_number,
    p.first_name, p.last_name, p.national_id,
    a.admission_date, a.expected_discharge_date,
    CASE 
        WHEN b.status = 'occupied' THEN 
            EXTRACT(days FROM (CURRENT_TIMESTAMP - a.admission_date))::INTEGER
        ELSE NULL 
    END as patient_los,
    br.reservation_code, br.requested_admission_date
FROM beds b
LEFT JOIN wards w ON b.ward_id = w.id
LEFT JOIN patients p ON b.current_patient_id = p.id
LEFT JOIN admissions a ON b.current_admission_id = a.id
LEFT JOIN bed_reservations br ON b.id = br.bed_id AND br.status IN ('active', 'confirmed')
WHERE b.is_active = true;

3. Ward Occupancy Summary View

CREATE VIEW v_ward_occupancy_summary AS
SELECT 
    w.id as ward_id,
    w.ward_code,
    w.ward_name,
    w.ward_type,
    w.total_beds,
    COUNT(b.id) as actual_beds,
    COUNT(CASE WHEN b.status = 'available' THEN 1 END) as available_beds,
    COUNT(CASE WHEN b.status = 'occupied' THEN 1 END) as occupied_beds,
    COUNT(CASE WHEN b.status = 'reserved' THEN 1 END) as reserved_beds,
    COUNT(CASE WHEN b.status = 'blocked' THEN 1 END) as blocked_beds,
    ROUND(
        (COUNT(CASE WHEN b.status = 'occupied' THEN 1 END)::DECIMAL / 
         NULLIF(COUNT(CASE WHEN b.status IN ('available', 'occupied', 'reserved') THEN 1 END), 0)) * 100, 
        2
    ) as occupancy_percentage
FROM wards w
LEFT JOIN beds b ON w.id = b.ward_id AND b.is_active = true
WHERE w.is_active = true
GROUP BY w.id, w.ward_code, w.ward_name, w.ward_type, w.total_beds;

🔧 Stored Procedures and Functions

1. Admission Processing Functions

Auto-assign Bed Function

CREATE OR REPLACE FUNCTION auto_assign_bed(
    p_patient_id UUID,
    p_ward_id UUID DEFAULT NULL,
    p_bed_type VARCHAR(30) DEFAULT 'standard',
    p_special_requirements TEXT DEFAULT NULL
) RETURNS UUID AS $$
DECLARE
    assigned_bed_id UUID;
    available_bed RECORD;
BEGIN
    -- Find best available bed
    SELECT b.id INTO assigned_bed_id
    FROM beds b
    JOIN wards w ON b.ward_id = w.id
    WHERE b.status = 'available'
      AND b.is_active = true
      AND (p_ward_id IS NULL OR b.ward_id = p_ward_id)
      AND (p_bed_type IS NULL OR b.bed_type = p_bed_type)
      AND b.blocked_until IS NULL OR b.blocked_until < CURRENT_TIMESTAMP
    ORDER BY 
        CASE WHEN b.ward_id = p_ward_id THEN 1 ELSE 2 END,
        CASE WHEN b.bed_type = p_bed_type THEN 1 ELSE 2 END,
        b.bed_code
    LIMIT 1;

    IF assigned_bed_id IS NOT NULL THEN
        -- Update bed status
        UPDATE beds 
        SET status = 'occupied',
            current_patient_id = p_patient_id,
            occupied_since = CURRENT_TIMESTAMP,
            status_changed_at = CURRENT_TIMESTAMP
        WHERE id = assigned_bed_id;
    END IF;

    RETURN assigned_bed_id;
END;
$$ LANGUAGE plpgsql;

Calculate Ward Statistics

CREATE OR REPLACE FUNCTION calculate_ward_statistics(
    p_ward_id UUID,
    p_date DATE DEFAULT CURRENT_DATE
) RETURNS TABLE (
    ward_id UUID,
    census_date DATE,
    total_beds INTEGER,
    occupied_beds INTEGER,
    occupancy_rate DECIMAL(5,2),
    new_admissions INTEGER,
    discharges INTEGER,
    avg_los DECIMAL(5,2)
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        p_ward_id,
        p_date,
        COUNT(b.id)::INTEGER as total_beds,
        COUNT(CASE WHEN b.status = 'occupied' THEN 1 END)::INTEGER as occupied_beds,
        ROUND((COUNT(CASE WHEN b.status = 'occupied' THEN 1 END)::DECIMAL / 
               NULLIF(COUNT(b.id), 0)) * 100, 2) as occupancy_rate,
        COUNT(CASE WHEN DATE(a.admission_date) = p_date THEN 1 END)::INTEGER as new_admissions,
        COUNT(CASE WHEN DATE(a.discharge_date) = p_date THEN 1 END)::INTEGER as discharges,
        ROUND(AVG(CASE WHEN a.status = 'discharged' THEN a.los_days END), 2) as avg_los
    FROM beds b
    LEFT JOIN admissions a ON b.current_admission_id = a.id
    WHERE b.ward_id = p_ward_id 
      AND b.is_active = true;
END;
$$ LANGUAGE plpgsql;

2. Trigger Functions

Update Bed Status on Admission

CREATE OR REPLACE FUNCTION update_bed_status_on_admission()
RETURNS TRIGGER AS $$
BEGIN
    -- Handle new admission
    IF TG_OP = 'INSERT' THEN
        -- Update bed status to occupied
        IF NEW.current_bed_id IS NOT NULL THEN
            UPDATE beds 
            SET status = 'occupied',
                current_patient_id = NEW.patient_id,
                current_admission_id = NEW.id,
                current_an = NEW.an,
                occupied_since = NEW.admission_date,
                status_changed_at = CURRENT_TIMESTAMP
            WHERE id = NEW.current_bed_id;
        END IF;
        RETURN NEW;
    END IF;

    -- Handle status changes
    IF TG_OP = 'UPDATE' THEN
        -- Discharge or transfer out
        IF OLD.status = 'active' AND NEW.status IN ('discharged', 'transferred_out') THEN
            UPDATE beds 
            SET status = 'available',
                current_patient_id = NULL,
                current_admission_id = NULL,
                current_an = NULL,
                occupied_since = NULL,
                status_changed_at = CURRENT_TIMESTAMP
            WHERE id = OLD.current_bed_id;
        END IF;

        -- Bed change
        IF OLD.current_bed_id != NEW.current_bed_id THEN
            -- Free old bed
            IF OLD.current_bed_id IS NOT NULL THEN
                UPDATE beds 
                SET status = 'available',
                    current_patient_id = NULL,
                    current_admission_id = NULL,
                    current_an = NULL,
                    occupied_since = NULL,
                    status_changed_at = CURRENT_TIMESTAMP
                WHERE id = OLD.current_bed_id;
            END IF;

            -- Occupy new bed
            IF NEW.current_bed_id IS NOT NULL THEN
                UPDATE beds 
                SET status = 'occupied',
                    current_patient_id = NEW.patient_id,
                    current_admission_id = NEW.id,
                    current_an = NEW.an,
                    occupied_since = CURRENT_TIMESTAMP,
                    status_changed_at = CURRENT_TIMESTAMP
                WHERE id = NEW.current_bed_id;
            END IF;
        END IF;

        RETURN NEW;
    END IF;

    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_update_bed_status_on_admission
    AFTER INSERT OR UPDATE ON admissions
    FOR EACH ROW
    EXECUTE FUNCTION update_bed_status_on_admission();

📊 Analytics and Reporting Support

1. Materialized Views for Performance

Daily Census Materialized View

CREATE MATERIALIZED VIEW mv_daily_census_summary AS
SELECT 
    DATE(a.admission_date) as census_date,
    w.id as ward_id,
    w.ward_name,
    w.ward_type,
    COUNT(DISTINCT a.id) FILTER (WHERE a.status = 'active') as current_census,
    COUNT(DISTINCT a.id) FILTER (WHERE DATE(a.admission_date) = DATE(a.admission_date)) as new_admissions,
    COUNT(DISTINCT a.id) FILTER (WHERE DATE(a.discharge_date) = DATE(a.admission_date)) as discharges,
    AVG(a.los_days) FILTER (WHERE a.status = 'discharged') as avg_los,
    COUNT(DISTINCT b.id) as total_beds,
    ROUND(
        (COUNT(DISTINCT a.id) FILTER (WHERE a.status = 'active')::DECIMAL / 
         COUNT(DISTINCT b.id)) * 100, 2
    ) as occupancy_rate
FROM wards w
LEFT JOIN beds b ON w.id = b.ward_id AND b.is_active = true
LEFT JOIN admissions a ON b.current_admission_id = a.id
WHERE w.is_active = true
  AND DATE(a.admission_date) >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY DATE(a.admission_date), w.id, w.ward_name, w.ward_type
ORDER BY census_date DESC, w.ward_name;

-- Refresh daily at 1 AM
CREATE INDEX ON mv_daily_census_summary (census_date, ward_id);

2. Performance Indexes for Large Scale

Partitioning for Large Tables

-- Partition admissions table by admission_date
ALTER TABLE admissions RENAME TO admissions_old;

CREATE TABLE admissions (LIKE admissions_old INCLUDING ALL) 
PARTITION BY RANGE (admission_date);

-- Create monthly partitions
CREATE TABLE admissions_y2024m08 PARTITION OF admissions
    FOR VALUES FROM ('2024-08-01') TO ('2024-09-01');

CREATE TABLE admissions_y2024m09 PARTITION OF admissions
    FOR VALUES FROM ('2024-09-01') TO ('2024-10-01');

-- Auto-create monthly partitions using pg_partman
SELECT partman.create_parent(
    p_parent_table => 'public.admissions',
    p_control => 'admission_date',
    p_type => 'range',
    p_interval => 'monthly'
);

🔐 Security and Access Control

Row Level Security (RLS)

Ward-based Access Control

-- Enable RLS on admissions
ALTER TABLE admissions ENABLE ROW LEVEL SECURITY;

-- Policy for ward-based access
CREATE POLICY admission_ward_access ON admissions
    FOR ALL TO medical_staff
    USING (
        current_ward_id IN (
            SELECT ward_id 
            FROM user_ward_assignments 
            WHERE user_id = current_setting('app.current_user_id')::UUID
        )
    );

-- Policy for admission staff (full access)
CREATE POLICY admission_staff_access ON admissions
    FOR ALL TO admission_staff
    USING (true);

-- Policy for bed management
ALTER TABLE beds ENABLE ROW LEVEL SECURITY;

CREATE POLICY bed_ward_access ON beds
    FOR ALL TO medical_staff
    USING (
        ward_id IN (
            SELECT ward_id 
            FROM user_ward_assignments 
            WHERE user_id = current_setting('app.current_user_id')::UUID
        )
    );

💾 Maintenance and Optimization

Automated Maintenance Tasks

-- Update daily census automatically
CREATE OR REPLACE FUNCTION update_daily_census()
RETURNS INTEGER AS $$
DECLARE
    processed_wards INTEGER := 0;
    ward_record RECORD;
BEGIN
    FOR ward_record IN SELECT id FROM wards WHERE is_active = true
    LOOP
        INSERT INTO daily_census (census_date, ward_id, total_beds, occupied_beds, occupancy_rate, midnight_census)
        SELECT 
            CURRENT_DATE,
            ward_record.id,
            COUNT(b.id),
            COUNT(CASE WHEN b.status = 'occupied' THEN 1 END),
            ROUND((COUNT(CASE WHEN b.status = 'occupied' THEN 1 END)::DECIMAL / COUNT(b.id)) * 100, 2),
            COUNT(CASE WHEN b.status = 'occupied' THEN 1 END)
        FROM beds b
        WHERE b.ward_id = ward_record.id AND b.is_active = true
        ON CONFLICT (census_date, ward_id) 
        DO UPDATE SET 
            total_beds = EXCLUDED.total_beds,
            occupied_beds = EXCLUDED.occupied_beds,
            occupancy_rate = EXCLUDED.occupancy_rate,
            midnight_census = EXCLUDED.midnight_census,
            updated_at = CURRENT_TIMESTAMP;

        processed_wards := processed_wards + 1;
    END LOOP;

    RETURN processed_wards;
END;
$$ LANGUAGE plpgsql;

-- Schedule census update daily at midnight
SELECT cron.schedule('update-daily-census', '0 0 * * *', 'SELECT update_daily_census();');

📝 Notes

Important Implementation Considerations:

  1. Real-time Updates: ใช้ WebSocket สำหรับ bed status updates
  2. Performance: Optimized สำหรับ healthcare workloads
  3. Multi-source Integration: รองรับข้อมูลจาก OPD, ER, Appointment
  4. Bed Management: Advanced bed allocation algorithms
  5. Compliance: ตาม Healthcare standards และ Patient safety requirements
  6. Scalability: Partitioned tables สำหรับ large hospitals

Cross-System References:

  • EMR Core Integration: patients, medical_visits, users, departments
  • OPD-CPOE Integration: visit_orders, visit_diagnoses
  • ER System Integration: er_cases, er_triage
  • IPD-CPOE Integration: ipd_orders, ipd_progress_notes
  • DRG System Integration: drg_cases
  • Financial Integration: billing_transactions, insurance_claims

ผู้จัดทำ: Claude Code - Senior Medical Technology Systems Analyst
วันที่: 28 สิงหาคม 2568
Version: 1.0