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

1539 lines
38 KiB
Markdown

# 📋 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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"name": "ANAK (Updated)",
"available": false,
"totalQuota": 1200,
"jamShiftList": [
{ "dari": "07:00", "sampai": "11:00", "kuota": 1200 }
]
}
```
**Response:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"clinics": [
{
"id": 1,
"kode": "AN",
"name": "ANAK",
// ... full clinic object
}
]
}
```
**Response:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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):**
```json
{
"schedules": [
{
"day": "Senin",
"shift": "Shift 1",
"startTime": "07:00",
"endTime": "11:00",
"quota": 15
}
]
}
```
**Response:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"day": "Selasa",
"shift": "Shift 2",
"startTime": "13:00",
"endTime": "17:00",
"quota": 20,
"available": true
}
```
**Response:**
```json
{
"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:**
```json
{
"doctors": [
{
"id": 1,
"name": "dr. Sarah Putri, Sp.A",
// ... full doctor object
}
]
}
```
**Response:**
```json
{
"success": true,
"data": [
// ... array of synced doctors
],
"message": "Batch sync berhasil. 50 doctors synced."
}
```
---
## 📋 DATA STRUCTURES
### **Clinic Object**
```typescript
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**
```typescript
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:
```json
{
"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):**
7.`POST /api/clinics` - Create clinic
8.`PATCH /api/clinics/:id` - Update clinic
9.`PATCH /api/clinics/:id/master-config` - Update master config
10.`POST /api/doctors` - Create doctor
11.`PATCH /api/doctors/:id` - Update doctor
### **Low Priority (Nice to Have):**
12.`DELETE /api/clinics/:id` - Delete clinic
13.`DELETE /api/doctors/:id` - Delete doctor
14.`POST /api/clinics/:clinicId/doctors/:doctorId` - Assign doctor
15.`DELETE /api/clinics/:clinicId/doctors/:doctorId` - Unassign doctor
16.`GET /api/doctors/:id/schedules` - Get doctor schedules
17.`POST /api/clinics/batch` - Batch sync clinics
18.`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:**
```sql
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:**
```sql
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:**
```sql
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:**
```sql
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:**
```sql
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:**
```sql
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)**
```sql
-- ============================================
-- 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)**
```sql
-- ============================================
-- 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**
```sql
-- 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**
#### **1. Get Clinic with All Related Data**
```sql
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**
```sql
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**
```sql
SELECT * FROM clinics WHERE available = true ORDER BY name;
```
#### **4. Get Clinics with Doctor Count**
```sql
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)
---
---