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 availablesearch(optional, string): Search by name atau kodepage(optional, number): Page number untuk paginationlimit(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 IDclinicKode(optional, string): Filter by clinic kodeavailable(optional, boolean): Filter by availabilitysearch(optional, string): Search by namepage(optional, number): Page numberlimit(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 IDdoctorId(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 IDdoctorId(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 clinicday(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 IDscheduleId(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- Success201 Created- Resource created400 Bad Request- Invalid request401 Unauthorized- Authentication required403 Forbidden- Insufficient permissions404 Not Found- Resource not found409 Conflict- Resource conflict (e.g., duplicate kode)500 Internal Server Error- Server error
📝 NOTES
- Pagination: Endpoints yang support pagination akan mengembalikan
paginationobject - Filtering: Query parameters untuk filtering bersifat optional
- Search: Search biasanya case-insensitive dan search di multiple fields
- Dates: Semua tanggal menggunakan format ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ)
- Times: Semua waktu menggunakan format 24-hour (HH:mm)
🚀 PRIORITY ENDPOINTS (Untuk Prototype)
High Priority (Must Have):
- ✅
GET /api/clinics- Get all clinics - ✅
GET /api/clinics/:id- Get clinic by ID - ✅
GET /api/clinics/kode/:kode- Get clinic by kode - ✅
GET /api/clinics/dropdown- Get clinics for dropdown - ✅
GET /api/clinics/:clinicId/doctors- Get doctors by clinic - ✅
GET /api/doctors- Get all doctors
Medium Priority (Should Have):
- ✅
POST /api/clinics- Create clinic - ✅
PATCH /api/clinics/:id- Update clinic - ✅
PATCH /api/clinics/:id/master-config- Update master config - ✅
POST /api/doctors- Create doctor - ✅
PATCH /api/doctors/:id- Update doctor
Low Priority (Nice to Have):
- ✅
DELETE /api/clinics/:id- Delete clinic - ✅
DELETE /api/doctors/:id- Delete doctor - ✅
POST /api/clinics/:clinicId/doctors/:doctorId- Assign doctor - ✅
DELETE /api/clinics/:clinicId/doctors/:doctorId- Unassign doctor - ✅
GET /api/doctors/:id/schedules- Get doctor schedules - ✅
POST /api/clinics/batch- Batch sync clinics - ✅
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 incrementkode(VARCHAR(10)) - Unique clinic code (e.g., "AN", "AS")name(VARCHAR(255)) - Clinic namesubtitle(VARCHAR(255)) - Optional subtitleicon(VARCHAR(100)) - Material Design icon nameshift(VARCHAR(50)) - Default shift (e.g., "SHIFT 1")schedule(VARCHAR(255)) - Schedule textavailable(BOOLEAN) - Is clinic availabletotal_quota(INTEGER) - Total quotaauto_shift(BOOLEAN) - Auto shift enabledcreated_at(TIMESTAMP) - Creation timestampupdated_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 keyclinic_id(BIGINT) - Foreign key to clinicsday_name(VARCHAR(20)) - Day name (e.g., "Senin", "Selasa")created_at(TIMESTAMP) - Creation timestampupdated_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 keyclinic_id(BIGINT) - Foreign key to clinicsdari(TIME) - Start timesampai(TIME) - End timekuota(INTEGER) - Quota for this time slotcreated_at(TIMESTAMP) - Creation timestampupdated_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 keyname(VARCHAR(255)) - Full doctor namespecialization(VARCHAR(100)) - Specializationnip(VARCHAR(50)) - NIP (unique)available(BOOLEAN) - Is doctor availablecreated_at(TIMESTAMP) - Creation timestampupdated_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 keyclinic_id(BIGINT) - Foreign key to clinicsdoctor_id(BIGINT) - Foreign key to doctorscreated_at(TIMESTAMP) - Creation timestampupdated_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 keydoctor_id(BIGINT) - Foreign key to doctorsclinic_id(BIGINT) - Foreign key to clinicsday_name(VARCHAR(20)) - Day nameshift(VARCHAR(50)) - Shift namestart_time(TIME) - Start timeend_time(TIME) - End timequota(INTEGER) - Quota for this scheduleavailable(BOOLEAN) - Is schedule availablecreated_at(TIMESTAMP) - Creation timestampupdated_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 uniquedoctors.nip- Must be unique (if provided)clinic_schedules(clinic_id, day_name)- Unique combinationclinic_doctors(clinic_id, doctor_id)- Unique combination
2. Foreign Key Constraints
- All foreign keys use
ON DELETE CASCADEto maintain referential integrity - When a clinic is deleted, all related records are automatically deleted
3. Data Validation Rules
kodemust be uppercase (enforce in application or use CHECK constraint)day_nameshould be one of: Senin, Selasa, Rabu, Kamis, Jum'at, Sabtu, Minggudaritime must be beforesampaitimequotamust 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
1. Get Clinic with All Related Data
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
- Database Type: PostgreSQL recommended (BIGSERIAL, TIMESTAMP support)
- Alternative: MySQL/MariaDB (use BIGINT AUTO_INCREMENT, DATETIME)
- Alternative: SQLite (use INTEGER PRIMARY KEY AUTOINCREMENT, TEXT for timestamps)
For MySQL/MariaDB:
- Replace
BIGSERIALwithBIGINT AUTO_INCREMENT - Replace
TIMESTAMPwithDATETIME - Replace
TIMEwithTIME(same) - Replace
CURRENT_TIMESTAMPwithNOW()
For SQLite:
- Replace
BIGSERIALwithINTEGER PRIMARY KEY AUTOINCREMENT - Replace
TIMESTAMPwithTEXT - Replace
TIMEwithTEXT - Use triggers for
updated_at(SQLite doesn't support function-based triggers)