Files
web-antrean/docs/API_ENDPOINTS_CLINIC_DOCTOR.md
T
2026-01-06 14:51:28 +07:00

38 KiB

📋 API Endpoints - Clinic (Poli) & Doctor

Dokumen ini berisi list lengkap endpoints API yang diperlukan untuk data Clinic (Poli) dan Doctor

📊 Base URL

Base URL: /api

🏥 CLINIC (POLI) ENDPOINTS

1. Get All Clinics

Endpoint: GET /api/clinics

Description: Mengambil semua data klinik/poli

Query Parameters:

  • available (optional, boolean): Filter berdasarkan status available
  • search (optional, string): Search by name atau kode
  • page (optional, number): Page number untuk pagination
  • limit (optional, number): Items per page

Response:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "kode": "AN",
      "name": "ANAK",
      "subtitle": "",
      "icon": "mdi-baby-face",
      "shift": "SHIFT 1",
      "schedule": "Mulai Pukul 07:00",
      "available": true,
      "doctors": ["dr. Sarah Putri, Sp.A", "dr. Andi Wijaya, Sp.A"],
      "shifts": [
        { "name": "Shift 1", "quota": 15 },
        { "name": "Shift 2", "quota": 20 }
      ],
      "totalQuota": 1000,
      "jamShiftList": [
        { "dari": "07:00", "sampai": "11:00", "kuota": 1000 }
      ],
      "autoShift": false,
      "jadwalKlinik": ["Senin", "Rabu", "Jumat"],
      "createdAt": "2024-01-01T00:00:00.000Z",
      "updatedAt": "2024-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 23,
    "totalPages": 3
  }
}

2. Get Clinic by ID

Endpoint: GET /api/clinics/:id

Description: Mengambil data klinik berdasarkan ID

Path Parameters:

  • id (number): Clinic ID

Response:

{
  "success": true,
  "data": {
    "id": 1,
    "kode": "AN",
    "name": "ANAK",
    "subtitle": "",
    "icon": "mdi-baby-face",
    "shift": "SHIFT 1",
    "schedule": "Mulai Pukul 07:00",
    "available": true,
    "doctors": ["dr. Sarah Putri, Sp.A", "dr. Andi Wijaya, Sp.A"],
    "shifts": [
      { "name": "Shift 1", "quota": 15 },
      { "name": "Shift 2", "quota": 20 }
    ],
    "totalQuota": 1000,
    "jamShiftList": [
      { "dari": "07:00", "sampai": "11:00", "kuota": 1000 }
    ],
    "autoShift": false,
    "jadwalKlinik": ["Senin", "Rabu", "Jumat"],
    "createdAt": "2024-01-01T00:00:00.000Z",
    "updatedAt": "2024-01-01T00:00:00.000Z"
  }
}

3. Get Clinic by Kode

Endpoint: GET /api/clinics/kode/:kode

Description: Mengambil data klinik berdasarkan kode

Path Parameters:

  • kode (string): Clinic kode (e.g., "AN", "AS", "BD")

Response:

{
  "success": true,
  "data": {
    "id": 1,
    "kode": "AN",
    "name": "ANAK",
    // ... same structure as Get by ID
  }
}

4. Get Clinics for Dropdown

Endpoint: GET /api/clinics/dropdown

Description: Mengambil data klinik dalam format yang dioptimalkan untuk dropdown

Query Parameters:

  • available (optional, boolean): Hanya ambil yang available

Response:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "kode": "AN",
      "name": "ANAK",
      "icon": "mdi-baby-face",
      "available": true
    }
  ]
}

5. Create Clinic

Endpoint: POST /api/clinics

Description: Membuat klinik baru

Request Body:

{
  "kode": "AN",
  "name": "ANAK",
  "subtitle": "",
  "icon": "mdi-baby-face",
  "shift": "SHIFT 1",
  "schedule": "Mulai Pukul 07:00",
  "available": true,
  "doctors": ["dr. Sarah Putri, Sp.A"],
  "shifts": [
    { "name": "Shift 1", "quota": 15 },
    { "name": "Shift 2", "quota": 20 }
  ],
  "totalQuota": 1000,
  "jamShiftList": [
    { "dari": "07:00", "sampai": "11:00", "kuota": 1000 }
  ],
  "autoShift": false,
  "jadwalKlinik": ["Senin", "Rabu", "Jumat"]
}

Response:

{
  "success": true,
  "data": {
    "id": 24,
    "kode": "AN",
    "name": "ANAK",
    // ... full clinic object
    "createdAt": "2024-01-01T00:00:00.000Z"
  },
  "message": "Clinic berhasil dibuat"
}

6. Update Clinic

Endpoint: PATCH /api/clinics/:id

Description: Update data klinik

Path Parameters:

  • id (number): Clinic ID

Request Body:

{
  "name": "ANAK (Updated)",
  "available": false,
  "totalQuota": 1200,
  "jamShiftList": [
    { "dari": "07:00", "sampai": "11:00", "kuota": 1200 }
  ]
}

Response:

{
  "success": true,
  "data": {
    "id": 1,
    // ... updated clinic object
    "updatedAt": "2024-01-01T00:00:00.000Z"
  },
  "message": "Clinic berhasil diupdate"
}

7. Update Clinic Master Config

Endpoint: PATCH /api/clinics/:id/master-config

Description: Update hanya master config (totalQuota, jamShiftList, autoShift, jadwalKlinik)

Path Parameters:

  • id (number): Clinic ID

Request Body:

{
  "totalQuota": 1200,
  "jamShiftList": [
    { "dari": "07:00", "sampai": "11:00", "kuota": 800 },
    { "dari": "13:00", "sampai": "16:00", "kuota": 400 }
  ],
  "autoShift": true,
  "jadwalKlinik": ["Senin", "Selasa", "Rabu", "Kamis", "Jum'at"]
}

Response:

{
  "success": true,
  "data": {
    "id": 1,
    // ... clinic object with updated master config
    "updatedAt": "2024-01-01T00:00:00.000Z"
  },
  "message": "Master config berhasil diupdate"
}

8. Delete Clinic

Endpoint: DELETE /api/clinics/:id

Description: Menghapus klinik

Path Parameters:

  • id (number): Clinic ID

Response:

{
  "success": true,
  "message": "Clinic berhasil dihapus"
}

9. Batch Sync Clinics

Endpoint: POST /api/clinics/batch

Description: Sync multiple clinics dengan database (untuk offline-first scenarios)

Request Body:

{
  "clinics": [
    {
      "id": 1,
      "kode": "AN",
      "name": "ANAK",
      // ... full clinic object
    }
  ]
}

Response:

{
  "success": true,
  "data": [
    // ... array of synced clinics
  ],
  "message": "Batch sync berhasil. 23 clinics synced."
}

👨‍⚕️ DOCTOR ENDPOINTS

10. Get All Doctors

Endpoint: GET /api/doctors

Description: Mengambil semua data dokter

Query Parameters:

  • clinicId (optional, number): Filter by clinic ID
  • clinicKode (optional, string): Filter by clinic kode
  • available (optional, boolean): Filter by availability
  • search (optional, string): Search by name
  • page (optional, number): Page number
  • limit (optional, number): Items per page

Response:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "dr. Sarah Putri, Sp.A",
      "specialization": "Sp.A",
      "nip": "123456789012345678",
      "clinicIds": [1],
      "clinics": [
        {
          "id": 1,
          "kode": "AN",
          "name": "ANAK"
        }
      ],
      "available": true,
      "schedules": [
        {
          "clinicId": 1,
          "day": "Senin",
          "shift": "Shift 1",
          "startTime": "07:00",
          "endTime": "11:00",
          "quota": 15
        }
      ],
      "createdAt": "2024-01-01T00:00:00.000Z",
      "updatedAt": "2024-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 50,
    "totalPages": 5
  }
}

11. Get Doctor by ID

Endpoint: GET /api/doctors/:id

Description: Mengambil data dokter berdasarkan ID

Path Parameters:

  • id (number): Doctor ID

Response:

{
  "success": true,
  "data": {
    "id": 1,
    "name": "dr. Sarah Putri, Sp.A",
    "specialization": "Sp.A",
    "nip": "123456789012345678",
    "clinicIds": [1],
    "clinics": [
      {
        "id": 1,
        "kode": "AN",
        "name": "ANAK"
      }
    ],
    "available": true,
    "schedules": [
      {
        "clinicId": 1,
        "day": "Senin",
        "shift": "Shift 1",
        "startTime": "07:00",
        "endTime": "11:00",
        "quota": 15
      }
    ],
    "createdAt": "2024-01-01T00:00:00.000Z",
    "updatedAt": "2024-01-01T00:00:00.000Z"
  }
}

12. Get Doctors by Clinic

Endpoint: GET /api/clinics/:clinicId/doctors

Description: Mengambil semua dokter yang terdaftar di klinik tertentu

Path Parameters:

  • clinicId (number): Clinic ID

Response:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "dr. Sarah Putri, Sp.A",
      "specialization": "Sp.A",
      "nip": "123456789012345678",
      "available": true,
      "schedules": [
        {
          "day": "Senin",
          "shift": "Shift 1",
          "startTime": "07:00",
          "endTime": "11:00",
          "quota": 15
        }
      ]
    }
  ]
}

13. Get Doctors by Clinic Kode

Endpoint: GET /api/clinics/kode/:kode/doctors

Description: Mengambil semua dokter yang terdaftar di klinik berdasarkan kode

Path Parameters:

  • kode (string): Clinic kode (e.g., "AN", "AS")

Response:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "dr. Sarah Putri, Sp.A",
      // ... same structure as Get Doctors by Clinic
    }
  ]
}

14. Create Doctor

Endpoint: POST /api/doctors

Description: Membuat dokter baru

Request Body:

{
  "name": "dr. Sarah Putri, Sp.A",
  "specialization": "Sp.A",
  "nip": "123456789012345678",
  "clinicIds": [1],
  "available": true,
  "schedules": [
    {
      "clinicId": 1,
      "day": "Senin",
      "shift": "Shift 1",
      "startTime": "07:00",
      "endTime": "11:00",
      "quota": 15
    }
  ]
}

Response:

{
  "success": true,
  "data": {
    "id": 51,
    "name": "dr. Sarah Putri, Sp.A",
    // ... full doctor object
    "createdAt": "2024-01-01T00:00:00.000Z"
  },
  "message": "Doctor berhasil dibuat"
}

15. Update Doctor

Endpoint: PATCH /api/doctors/:id

Description: Update data dokter

Path Parameters:

  • id (number): Doctor ID

Request Body:

{
  "name": "dr. Sarah Putri, Sp.A (Updated)",
  "available": false,
  "schedules": [
    {
      "clinicId": 1,
      "day": "Senin",
      "shift": "Shift 1",
      "startTime": "08:00",
      "endTime": "12:00",
      "quota": 20
    }
  ]
}

Response:

{
  "success": true,
  "data": {
    "id": 1,
    // ... updated doctor object
    "updatedAt": "2024-01-01T00:00:00.000Z"
  },
  "message": "Doctor berhasil diupdate"
}

16. Delete Doctor

Endpoint: DELETE /api/doctors/:id

Description: Menghapus dokter

Path Parameters:

  • id (number): Doctor ID

Response:

{
  "success": true,
  "message": "Doctor berhasil dihapus"
}

17. Assign Doctor to Clinic

Endpoint: POST /api/clinics/:clinicId/doctors/:doctorId

Description: Assign dokter ke klinik

Path Parameters:

  • clinicId (number): Clinic ID
  • doctorId (number): Doctor ID

Request Body (optional):

{
  "schedules": [
    {
      "day": "Senin",
      "shift": "Shift 1",
      "startTime": "07:00",
      "endTime": "11:00",
      "quota": 15
    }
  ]
}

Response:

{
  "success": true,
  "data": {
    "clinicId": 1,
    "doctorId": 1,
    "schedules": [
      {
        "day": "Senin",
        "shift": "Shift 1",
        "startTime": "07:00",
        "endTime": "11:00",
        "quota": 15
      }
    ]
  },
  "message": "Doctor berhasil di-assign ke clinic"
}

18. Unassign Doctor from Clinic

Endpoint: DELETE /api/clinics/:clinicId/doctors/:doctorId

Description: Unassign dokter dari klinik

Path Parameters:

  • clinicId (number): Clinic ID
  • doctorId (number): Doctor ID

Response:

{
  "success": true,
  "message": "Doctor berhasil di-unassign dari clinic"
}

19. Get Doctor Schedules

Endpoint: GET /api/doctors/:id/schedules

Description: Mengambil jadwal dokter

Path Parameters:

  • id (number): Doctor ID

Query Parameters:

  • clinicId (optional, number): Filter by clinic
  • day (optional, string): Filter by day (e.g., "Senin")
  • date (optional, string): Filter by specific date (YYYY-MM-DD)

Response:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "doctorId": 1,
      "clinicId": 1,
      "clinic": {
        "id": 1,
        "kode": "AN",
        "name": "ANAK"
      },
      "day": "Senin",
      "shift": "Shift 1",
      "startTime": "07:00",
      "endTime": "11:00",
      "quota": 15,
      "available": true,
      "createdAt": "2024-01-01T00:00:00.000Z"
    }
  ]
}

20. Update Doctor Schedule

Endpoint: PATCH /api/doctors/:doctorId/schedules/:scheduleId

Description: Update jadwal dokter

Path Parameters:

  • doctorId (number): Doctor ID
  • scheduleId (number): Schedule ID

Request Body:

{
  "day": "Selasa",
  "shift": "Shift 2",
  "startTime": "13:00",
  "endTime": "17:00",
  "quota": 20,
  "available": true
}

Response:

{
  "success": true,
  "data": {
    "id": 1,
    // ... updated schedule object
    "updatedAt": "2024-01-01T00:00:00.000Z"
  },
  "message": "Schedule berhasil diupdate"
}

21. Batch Sync Doctors

Endpoint: POST /api/doctors/batch

Description: Sync multiple doctors dengan database

Request Body:

{
  "doctors": [
    {
      "id": 1,
      "name": "dr. Sarah Putri, Sp.A",
      // ... full doctor object
    }
  ]
}

Response:

{
  "success": true,
  "data": [
    // ... array of synced doctors
  ],
  "message": "Batch sync berhasil. 50 doctors synced."
}

📋 DATA STRUCTURES

Clinic Object

interface Clinic {
  id: number;
  kode: string;                    // Unique code (e.g., "AN", "AS")
  name: string;                    // Clinic name
  subtitle?: string;               // Optional subtitle
  icon?: string;                   // Material Design icon name
  shift?: string;                  // Default shift (e.g., "SHIFT 1")
  schedule?: string;               // Schedule text (e.g., "Mulai Pukul 07:00")
  available: boolean;              // Is clinic available
  doctors?: string[];              // Array of doctor names (legacy format)
  shifts?: Array<{                 // Shift quotas
    name: string;
    quota: number;
  }>;
  // Master config
  totalQuota?: number;             // Total quota
  jamShiftList?: Array<{           // Time slots with quotas
    dari: string;                  // Start time (HH:mm)
    sampai: string;                // End time (HH:mm)
    kuota: number;                 // Quota for this time slot
  }>;
  autoShift?: boolean;             // Auto shift enabled
  jadwalKlinik?: string[];        // Operating days (e.g., ["Senin", "Rabu"])
  createdAt: string;               // ISO 8601 timestamp
  updatedAt: string;               // ISO 8601 timestamp
}

Doctor Object

interface Doctor {
  id: number;
  name: string;                    // Full name (e.g., "dr. Sarah Putri, Sp.A")
  specialization?: string;        // Specialization (e.g., "Sp.A")
  nip?: string;                    // NIP (Nomor Induk Pegawai)
  clinicIds?: number[];            // Array of clinic IDs
  clinics?: Array<{                // Clinic details
    id: number;
    kode: string;
    name: string;
  }>;
  available: boolean;               // Is doctor available
  schedules?: Array<{               // Doctor schedules
    id?: number;
    clinicId: number;
    day: string;                   // Day name (e.g., "Senin")
    shift: string;                 // Shift name (e.g., "Shift 1")
    startTime: string;             // Start time (HH:mm)
    endTime: string;               // End time (HH:mm)
    quota: number;                 // Quota for this schedule
    available?: boolean;            // Is this schedule available
  }>;
  createdAt: string;               // ISO 8601 timestamp
  updatedAt: string;               // ISO 8601 timestamp
}

🔐 AUTHENTICATION & AUTHORIZATION

Semua endpoints memerlukan authentication token (jika diperlukan):

Headers:

Authorization: Bearer <token>
Content-Type: application/json

⚠️ ERROR RESPONSES

Semua endpoints mengembalikan error dalam format yang konsisten:

{
  "success": false,
  "error": "Error message",
  "code": "ERROR_CODE",
  "details": {
    // Additional error details
  }
}

Common HTTP Status Codes:

  • 200 OK - Success
  • 201 Created - Resource created
  • 400 Bad Request - Invalid request
  • 401 Unauthorized - Authentication required
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Resource not found
  • 409 Conflict - Resource conflict (e.g., duplicate kode)
  • 500 Internal Server Error - Server error

📝 NOTES

  1. Pagination: Endpoints yang support pagination akan mengembalikan pagination object
  2. Filtering: Query parameters untuk filtering bersifat optional
  3. Search: Search biasanya case-insensitive dan search di multiple fields
  4. Dates: Semua tanggal menggunakan format ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ)
  5. Times: Semua waktu menggunakan format 24-hour (HH:mm)

🚀 PRIORITY ENDPOINTS (Untuk Prototype)

High Priority (Must Have):

  1. GET /api/clinics - Get all clinics
  2. GET /api/clinics/:id - Get clinic by ID
  3. GET /api/clinics/kode/:kode - Get clinic by kode
  4. GET /api/clinics/dropdown - Get clinics for dropdown
  5. GET /api/clinics/:clinicId/doctors - Get doctors by clinic
  6. GET /api/doctors - Get all doctors

Medium Priority (Should Have):

  1. POST /api/clinics - Create clinic
  2. PATCH /api/clinics/:id - Update clinic
  3. PATCH /api/clinics/:id/master-config - Update master config
  4. POST /api/doctors - Create doctor
  5. PATCH /api/doctors/:id - Update doctor

Low Priority (Nice to Have):

  1. DELETE /api/clinics/:id - Delete clinic
  2. DELETE /api/doctors/:id - Delete doctor
  3. POST /api/clinics/:clinicId/doctors/:doctorId - Assign doctor
  4. DELETE /api/clinics/:clinicId/doctors/:doctorId - Unassign doctor
  5. GET /api/doctors/:id/schedules - Get doctor schedules
  6. POST /api/clinics/batch - Batch sync clinics
  7. POST /api/doctors/batch - Batch sync doctors


🗄️ DATABASE SCHEMA DESIGN

Bagian ini berisi referensi lengkap untuk Database Specialist untuk mengatur struktur database.


Database Tables

1. Table: clinics

Description: Tabel untuk menyimpan data klinik/poli

Schema:

CREATE TABLE clinics (
  id BIGSERIAL PRIMARY KEY,
  kode VARCHAR(10) UNIQUE NOT NULL,
  name VARCHAR(255) NOT NULL,
  subtitle VARCHAR(255) DEFAULT '',
  icon VARCHAR(100) DEFAULT NULL,
  shift VARCHAR(50) DEFAULT NULL,
  schedule VARCHAR(255) DEFAULT NULL,
  available BOOLEAN DEFAULT true,
  total_quota INTEGER DEFAULT 0,
  auto_shift BOOLEAN DEFAULT false,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes
CREATE INDEX idx_clinics_kode ON clinics(kode);
CREATE INDEX idx_clinics_available ON clinics(available);
CREATE INDEX idx_clinics_name ON clinics(name);

Columns:

  • id (BIGSERIAL) - Primary key, auto increment
  • kode (VARCHAR(10)) - Unique clinic code (e.g., "AN", "AS")
  • name (VARCHAR(255)) - Clinic name
  • subtitle (VARCHAR(255)) - Optional subtitle
  • icon (VARCHAR(100)) - Material Design icon name
  • shift (VARCHAR(50)) - Default shift (e.g., "SHIFT 1")
  • schedule (VARCHAR(255)) - Schedule text
  • available (BOOLEAN) - Is clinic available
  • total_quota (INTEGER) - Total quota
  • auto_shift (BOOLEAN) - Auto shift enabled
  • created_at (TIMESTAMP) - Creation timestamp
  • updated_at (TIMESTAMP) - Last update timestamp

2. Table: clinic_schedules

Description: Tabel untuk menyimpan jadwal operasional klinik

Schema:

CREATE TABLE clinic_schedules (
  id BIGSERIAL PRIMARY KEY,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  day_name VARCHAR(20) NOT NULL, -- e.g., "Senin", "Selasa"
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(clinic_id, day_name)
);

-- Indexes
CREATE INDEX idx_clinic_schedules_clinic_id ON clinic_schedules(clinic_id);
CREATE INDEX idx_clinic_schedules_day ON clinic_schedules(day_name);

Columns:

  • id (BIGSERIAL) - Primary key
  • clinic_id (BIGINT) - Foreign key to clinics
  • day_name (VARCHAR(20)) - Day name (e.g., "Senin", "Selasa")
  • created_at (TIMESTAMP) - Creation timestamp
  • updated_at (TIMESTAMP) - Last update timestamp

3. Table: clinic_shift_schedules

Description: Tabel untuk menyimpan jam shift dan kuota per shift

Schema:

CREATE TABLE clinic_shift_schedules (
  id BIGSERIAL PRIMARY KEY,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  dari TIME NOT NULL, -- Start time (e.g., "07:00")
  sampai TIME NOT NULL, -- End time (e.g., "11:00")
  kuota INTEGER NOT NULL DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes
CREATE INDEX idx_clinic_shift_schedules_clinic_id ON clinic_shift_schedules(clinic_id);

Columns:

  • id (BIGSERIAL) - Primary key
  • clinic_id (BIGINT) - Foreign key to clinics
  • dari (TIME) - Start time
  • sampai (TIME) - End time
  • kuota (INTEGER) - Quota for this time slot
  • created_at (TIMESTAMP) - Creation timestamp
  • updated_at (TIMESTAMP) - Last update timestamp

4. Table: doctors

Description: Tabel untuk menyimpan data dokter

Schema:

CREATE TABLE doctors (
  id BIGSERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  specialization VARCHAR(100) DEFAULT NULL, -- e.g., "Sp.A", "Sp.B"
  nip VARCHAR(50) UNIQUE DEFAULT NULL, -- Nomor Induk Pegawai
  available BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes
CREATE INDEX idx_doctors_name ON doctors(name);
CREATE INDEX idx_doctors_specialization ON doctors(specialization);
CREATE INDEX idx_doctors_available ON doctors(available);
CREATE INDEX idx_doctors_nip ON doctors(nip);

Columns:

  • id (BIGSERIAL) - Primary key
  • name (VARCHAR(255)) - Full doctor name
  • specialization (VARCHAR(100)) - Specialization
  • nip (VARCHAR(50)) - NIP (unique)
  • available (BOOLEAN) - Is doctor available
  • created_at (TIMESTAMP) - Creation timestamp
  • updated_at (TIMESTAMP) - Last update timestamp

5. Table: clinic_doctors

Description: Tabel junction untuk relasi many-to-many antara clinics dan doctors

Schema:

CREATE TABLE clinic_doctors (
  id BIGSERIAL PRIMARY KEY,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  doctor_id BIGINT NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(clinic_id, doctor_id)
);

-- Indexes
CREATE INDEX idx_clinic_doctors_clinic_id ON clinic_doctors(clinic_id);
CREATE INDEX idx_clinic_doctors_doctor_id ON clinic_doctors(doctor_id);
CREATE INDEX idx_clinic_doctors_composite ON clinic_doctors(clinic_id, doctor_id);

Columns:

  • id (BIGSERIAL) - Primary key
  • clinic_id (BIGINT) - Foreign key to clinics
  • doctor_id (BIGINT) - Foreign key to doctors
  • created_at (TIMESTAMP) - Creation timestamp
  • updated_at (TIMESTAMP) - Last update timestamp

6. Table: doctor_schedules

Description: Tabel untuk menyimpan jadwal dokter per klinik

Schema:

CREATE TABLE doctor_schedules (
  id BIGSERIAL PRIMARY KEY,
  doctor_id BIGINT NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  day_name VARCHAR(20) NOT NULL, -- e.g., "Senin", "Selasa"
  shift VARCHAR(50) NOT NULL, -- e.g., "Shift 1", "Shift 2"
  start_time TIME NOT NULL, -- e.g., "07:00"
  end_time TIME NOT NULL, -- e.g., "11:00"
  quota INTEGER NOT NULL DEFAULT 0,
  available BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes
CREATE INDEX idx_doctor_schedules_doctor_id ON doctor_schedules(doctor_id);
CREATE INDEX idx_doctor_schedules_clinic_id ON doctor_schedules(clinic_id);
CREATE INDEX idx_doctor_schedules_day ON doctor_schedules(day_name);
CREATE INDEX idx_doctor_schedules_composite ON doctor_schedules(doctor_id, clinic_id, day_name);

Columns:

  • id (BIGSERIAL) - Primary key
  • doctor_id (BIGINT) - Foreign key to doctors
  • clinic_id (BIGINT) - Foreign key to clinics
  • day_name (VARCHAR(20)) - Day name
  • shift (VARCHAR(50)) - Shift name
  • start_time (TIME) - Start time
  • end_time (TIME) - End time
  • quota (INTEGER) - Quota for this schedule
  • available (BOOLEAN) - Is schedule available
  • created_at (TIMESTAMP) - Creation timestamp
  • updated_at (TIMESTAMP) - Last update timestamp

Database Relationships (ERD)

┌─────────────┐
│   clinics   │
├─────────────┤
│ id (PK)     │
│ kode (UK)   │
│ name        │
│ ...         │
└──────┬──────┘
       │
       │ 1:N
       │
       ├─────────────────┐
       │                 │
       ▼                 ▼
┌──────────────────┐  ┌──────────────────────┐
│clinic_schedules  │  │clinic_shift_schedules │
├──────────────────┤  ├──────────────────────┤
│ id (PK)          │  │ id (PK)              │
│ clinic_id (FK)   │  │ clinic_id (FK)       │
│ day_name         │  │ dari                 │
└──────────────────┘  │ sampai               │
                      │ kuota                │
                      └──────────────────────┘
       │
       │ N:M
       │
       ▼
┌──────────────────┐
│ clinic_doctors   │
├──────────────────┤
│ id (PK)          │
│ clinic_id (FK)   │──┐
│ doctor_id (FK)   │──┤
└──────────────────┘  │
                      │
       ┌──────────────┘
       │
       │ N:1
       │
       ▼
┌─────────────┐
│   doctors   │
├─────────────┤
│ id (PK)     │
│ name        │
│ nip (UK)    │
│ ...         │
└──────┬──────┘
       │
       │ 1:N
       │
       ▼
┌──────────────────┐
│doctor_schedules  │
├──────────────────┤
│ id (PK)          │
│ doctor_id (FK)   │
│ clinic_id (FK)   │
│ day_name         │
│ shift            │
│ start_time       │
│ end_time         │
│ quota            │
└──────────────────┘

Complete SQL Schema (PostgreSQL)

-- ============================================
-- CLINICS TABLE
-- ============================================
CREATE TABLE clinics (
  id BIGSERIAL PRIMARY KEY,
  kode VARCHAR(10) UNIQUE NOT NULL,
  name VARCHAR(255) NOT NULL,
  subtitle VARCHAR(255) DEFAULT '',
  icon VARCHAR(100) DEFAULT NULL,
  shift VARCHAR(50) DEFAULT NULL,
  schedule VARCHAR(255) DEFAULT NULL,
  available BOOLEAN DEFAULT true,
  total_quota INTEGER DEFAULT 0,
  auto_shift BOOLEAN DEFAULT false,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_clinics_kode ON clinics(kode);
CREATE INDEX idx_clinics_available ON clinics(available);
CREATE INDEX idx_clinics_name ON clinics(name);

-- ============================================
-- CLINIC SCHEDULES TABLE (Operating Days)
-- ============================================
CREATE TABLE clinic_schedules (
  id BIGSERIAL PRIMARY KEY,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  day_name VARCHAR(20) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(clinic_id, day_name)
);

CREATE INDEX idx_clinic_schedules_clinic_id ON clinic_schedules(clinic_id);
CREATE INDEX idx_clinic_schedules_day ON clinic_schedules(day_name);

-- ============================================
-- CLINIC SHIFT SCHEDULES TABLE (Time Slots)
-- ============================================
CREATE TABLE clinic_shift_schedules (
  id BIGSERIAL PRIMARY KEY,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  dari TIME NOT NULL,
  sampai TIME NOT NULL,
  kuota INTEGER NOT NULL DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_clinic_shift_schedules_clinic_id ON clinic_shift_schedules(clinic_id);

-- ============================================
-- DOCTORS TABLE
-- ============================================
CREATE TABLE doctors (
  id BIGSERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  specialization VARCHAR(100) DEFAULT NULL,
  nip VARCHAR(50) UNIQUE DEFAULT NULL,
  available BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_doctors_name ON doctors(name);
CREATE INDEX idx_doctors_specialization ON doctors(specialization);
CREATE INDEX idx_doctors_available ON doctors(available);
CREATE INDEX idx_doctors_nip ON doctors(nip);

-- ============================================
-- CLINIC DOCTORS TABLE (Junction Table)
-- ============================================
CREATE TABLE clinic_doctors (
  id BIGSERIAL PRIMARY KEY,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  doctor_id BIGINT NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(clinic_id, doctor_id)
);

CREATE INDEX idx_clinic_doctors_clinic_id ON clinic_doctors(clinic_id);
CREATE INDEX idx_clinic_doctors_doctor_id ON clinic_doctors(doctor_id);
CREATE INDEX idx_clinic_doctors_composite ON clinic_doctors(clinic_id, doctor_id);

-- ============================================
-- DOCTOR SCHEDULES TABLE
-- ============================================
CREATE TABLE doctor_schedules (
  id BIGSERIAL PRIMARY KEY,
  doctor_id BIGINT NOT NULL REFERENCES doctors(id) ON DELETE CASCADE,
  clinic_id BIGINT NOT NULL REFERENCES clinics(id) ON DELETE CASCADE,
  day_name VARCHAR(20) NOT NULL,
  shift VARCHAR(50) NOT NULL,
  start_time TIME NOT NULL,
  end_time TIME NOT NULL,
  quota INTEGER NOT NULL DEFAULT 0,
  available BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_doctor_schedules_doctor_id ON doctor_schedules(doctor_id);
CREATE INDEX idx_doctor_schedules_clinic_id ON doctor_schedules(clinic_id);
CREATE INDEX idx_doctor_schedules_day ON doctor_schedules(day_name);
CREATE INDEX idx_doctor_schedules_composite ON doctor_schedules(doctor_id, clinic_id, day_name);

-- ============================================
-- TRIGGERS FOR UPDATED_AT
-- ============================================
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = CURRENT_TIMESTAMP;
  RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_clinics_updated_at BEFORE UPDATE ON clinics
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_clinic_schedules_updated_at BEFORE UPDATE ON clinic_schedules
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_clinic_shift_schedules_updated_at BEFORE UPDATE ON clinic_shift_schedules
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_doctors_updated_at BEFORE UPDATE ON doctors
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_clinic_doctors_updated_at BEFORE UPDATE ON clinic_doctors
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_doctor_schedules_updated_at BEFORE UPDATE ON doctor_schedules
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

Sample Data (Seed Data)

-- ============================================
-- SAMPLE CLINICS
-- ============================================
INSERT INTO clinics (kode, name, icon, shift, schedule, available, total_quota, auto_shift) VALUES
('AN', 'ANAK', 'mdi-baby-face', 'SHIFT 1', 'Mulai Pukul 07:00', true, 1000, false),
('AS', 'ANESTESI', 'mdi-face-mask', 'SHIFT 1', 'Mulai Pukul 07:00', true, 1500, false),
('BD', 'BEDAH', 'mdi-medical-bag', 'SHIFT 1', 'Mulai Pukul 07:00', true, 800, false),
('IP', 'IPD (PENYAKIT DALAM)', 'mdi-hospital', 'SHIFT 1', 'Mulai Pukul 07:00', true, 900, false),
('OB', 'KANDUNGAN', 'mdi-human-pregnant', 'SHIFT 1', 'Mulai Pukul 07:00', true, 1000, false);

-- ============================================
-- SAMPLE CLINIC SCHEDULES
-- ============================================
INSERT INTO clinic_schedules (clinic_id, day_name) VALUES
(1, 'Senin'),
(1, 'Rabu'),
(1, 'Jumat'),
(2, 'Senin'),
(2, 'Selasa'),
(2, 'Rabu'),
(2, 'Kamis'),
(2, 'Jum''at');

-- ============================================
-- SAMPLE CLINIC SHIFT SCHEDULES
-- ============================================
INSERT INTO clinic_shift_schedules (clinic_id, dari, sampai, kuota) VALUES
(1, '07:00', '11:00', 1000),
(2, '07:00', '11:00', 1000),
(2, '13:00', '16:00', 500),
(3, '07:00', '11:00', 800);

-- ============================================
-- SAMPLE DOCTORS
-- ============================================
INSERT INTO doctors (name, specialization, nip, available) VALUES
('dr. Sarah Putri, Sp.A', 'Sp.A', '123456789012345678', true),
('dr. Andi Wijaya, Sp.A', 'Sp.A', '123456789012345679', true),
('dr. Ahmad Fauzi, Sp.An', 'Sp.An', '123456789012345680', true),
('dr. Budi Santoso, Sp.B', 'Sp.B', '123456789012345681', true);

-- ============================================
-- SAMPLE CLINIC DOCTORS (Assignments)
-- ============================================
INSERT INTO clinic_doctors (clinic_id, doctor_id) VALUES
(1, 1), -- dr. Sarah Putri assigned to ANAK
(1, 2), -- dr. Andi Wijaya assigned to ANAK
(2, 3), -- dr. Ahmad Fauzi assigned to ANESTESI
(3, 4); -- dr. Budi Santoso assigned to BEDAH

-- ============================================
-- SAMPLE DOCTOR SCHEDULES
-- ============================================
INSERT INTO doctor_schedules (doctor_id, clinic_id, day_name, shift, start_time, end_time, quota, available) VALUES
(1, 1, 'Senin', 'Shift 1', '07:00', '11:00', 15, true),
(1, 1, 'Rabu', 'Shift 1', '07:00', '11:00', 15, true),
(2, 1, 'Senin', 'Shift 2', '13:00', '17:00', 20, true),
(3, 2, 'Senin', 'Shift 1', '07:00', '11:00', 0, true);

Database Constraints & Rules

1. Unique Constraints

  • clinics.kode - Must be unique
  • doctors.nip - Must be unique (if provided)
  • clinic_schedules(clinic_id, day_name) - Unique combination
  • clinic_doctors(clinic_id, doctor_id) - Unique combination

2. Foreign Key Constraints

  • All foreign keys use ON DELETE CASCADE to maintain referential integrity
  • When a clinic is deleted, all related records are automatically deleted

3. Data Validation Rules

  • kode must be uppercase (enforce in application or use CHECK constraint)
  • day_name should be one of: Senin, Selasa, Rabu, Kamis, Jum'at, Sabtu, Minggu
  • dari time must be before sampai time
  • quota must be >= 0

4. Optional CHECK Constraints

-- Ensure dari < sampai for clinic_shift_schedules
ALTER TABLE clinic_shift_schedules
ADD CONSTRAINT check_time_range CHECK (dari < sampai);

-- Ensure quota >= 0
ALTER TABLE clinic_shift_schedules
ADD CONSTRAINT check_quota_positive CHECK (kuota >= 0);

ALTER TABLE doctor_schedules
ADD CONSTRAINT check_doctor_quota_positive CHECK (quota >= 0);

-- Ensure valid day names (optional)
ALTER TABLE clinic_schedules
ADD CONSTRAINT check_valid_day CHECK (day_name IN ('Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum''at', 'Sabtu', 'Minggu'));

ALTER TABLE doctor_schedules
ADD CONSTRAINT check_valid_doctor_day CHECK (day_name IN ('Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum''at', 'Sabtu', 'Minggu'));

Query Examples for Common Operations

SELECT 
  c.*,
  ARRAY_AGG(DISTINCT cs.day_name) as jadwal_klinik,
  ARRAY_AGG(DISTINCT jsonb_build_object(
    'dari', css.dari,
    'sampai', css.sampai,
    'kuota', css.kuota
  )) as jam_shift_list,
  ARRAY_AGG(DISTINCT d.name) as doctors
FROM clinics c
LEFT JOIN clinic_schedules cs ON c.id = cs.clinic_id
LEFT JOIN clinic_shift_schedules css ON c.id = css.clinic_id
LEFT JOIN clinic_doctors cd ON c.id = cd.clinic_id
LEFT JOIN doctors d ON cd.doctor_id = d.id
WHERE c.id = 1
GROUP BY c.id;

2. Get Doctors by Clinic

SELECT 
  d.*,
  ARRAY_AGG(DISTINCT jsonb_build_object(
    'day', ds.day_name,
    'shift', ds.shift,
    'startTime', ds.start_time,
    'endTime', ds.end_time,
    'quota', ds.quota
  )) as schedules
FROM doctors d
INNER JOIN clinic_doctors cd ON d.id = cd.doctor_id
LEFT JOIN doctor_schedules ds ON d.id = ds.doctor_id AND cd.clinic_id = ds.clinic_id
WHERE cd.clinic_id = 1
GROUP BY d.id;

3. Get Available Clinics

SELECT * FROM clinics WHERE available = true ORDER BY name;

4. Get Clinics with Doctor Count

SELECT 
  c.*,
  COUNT(DISTINCT cd.doctor_id) as doctor_count
FROM clinics c
LEFT JOIN clinic_doctors cd ON c.id = cd.clinic_id
GROUP BY c.id;

Migration Notes

  1. Database Type: PostgreSQL recommended (BIGSERIAL, TIMESTAMP support)
  2. Alternative: MySQL/MariaDB (use BIGINT AUTO_INCREMENT, DATETIME)
  3. Alternative: SQLite (use INTEGER PRIMARY KEY AUTOINCREMENT, TEXT for timestamps)

For MySQL/MariaDB:

  • Replace BIGSERIAL with BIGINT AUTO_INCREMENT
  • Replace TIMESTAMP with DATETIME
  • Replace TIME with TIME (same)
  • Replace CURRENT_TIMESTAMP with NOW()

For SQLite:

  • Replace BIGSERIAL with INTEGER PRIMARY KEY AUTOINCREMENT
  • Replace TIMESTAMP with TEXT
  • Replace TIME with TEXT
  • Use triggers for updated_at (SQLite doesn't support function-based triggers)