1539 lines
38 KiB
Markdown
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)
|
|
|
|
---
|
|
|
|
---
|