# Struktur Database PPDB YAPIRI

## Overview
Dokumentasi ini menjelaskan struktur database lengkap sistem PPDB (Penerimaan Peserta Didik Baru) MIS YAPIRI setelah audit dan perbaikan pada 16 Juni 2026.

---

## Core Tables

### 1. **users** (Authentication & User Management)
**Purpose**: Menyimpan data pengguna (pendaftar dan admin/superadmin)

**Primary Key**: `id_user` VARCHAR(20) - Alphanumeric (e.g., "HUS00626")

**Columns**:
- `id_user` - Primary key, alphanumeric, auto-generated dari nama
- `nama_pendaftar` - Nama lengkap user
- `email` - Email (UNIQUE)
- `username` - Username untuk admin (NULLABLE untuk pendaftar)
- `password` - Hashed password
- `no_hp` - Nomor telepon
- `role` - ENUM('admin', 'superadmin', 'pendaftar')
- `status_langkah` - ENUM(11 tahapan) - Status pendaftaran saat ini
- `status_seleksi` - ENUM('proses', 'diterima', 'tidak_diterima', 'daftar_tunggu')
- `email_verified_at` - Timestamp verifikasi email
- `verification_token` - Token untuk verifikasi email
- `timestamps`

**Foreign Keys**: None (root table)

**Indexes**:
- `idx_users_status_langkah` ON `status_langkah`
- `idx_users_status_seleksi` ON `status_seleksi`
- `idx_users_role_created` ON `role`, `created_at`

**Relations**:
- Has Many: pendaftaran, pembayaran

---

### 2. **pendaftaran** (Registration Records)
**Purpose**: Menyimpan catatan pendaftaran siswa (bisa multiple per user di tahun berbeda)

**Primary Key**: `id_pendaftaran` VARCHAR(20) - Alphanumeric

**Columns**:
- `id_pendaftaran` - Primary key, alphanumeric, auto-generated (Format: [User prefix]R[counter], e.g., HUSR001)
- `id_user` - Foreign key ke users
- `gelombang` - INT (1-3) - Gelombang pendaftaran
- `tahun_ajaran` - YEAR (2026)
- `tahap_aktif` - TINYINT (1-11) - Numeric representation of status_langkah
- `status_pendaftaran` - ENUM(13 status) - Overall registration status
- `tgl_daftar` - Timestamp pendaftaran
- `tgl_selesai` - Timestamp selesai (NULLABLE)
- `timestamps`

**Foreign Keys**:
- `id_user` → users.id_user (ON DELETE CASCADE)

**Indexes**:
- `idx_pendaftaran_user_gelombang` ON `id_user`, `gelombang`
- ON `status_pendaftaran`
- ON `tahap_aktif`

**Relations**:
- Belongs To: users
- Has Many: pembayaran
- Has One: biodata_siswa, bakat_anak, jadwal_interaksi

---

### 3. **pembayaran** (Payment Records)
**Purpose**: Menyimpan transaksi pembayaran (pendaftaran + uang pangkal)

**Primary Key**: `id_pembayaran` VARCHAR(20) - Alphanumeric

**Columns**:
- `id_pembayaran` - Primary key, alphanumeric, internal ID (Format: [User prefix]P[counter], e.g., JOKP001)
- `kode_pembayaran` - Invoice number, user-facing (UNIQUE), format: INV/YYYY/MM/XXXXX (e.g., INV/2026/06/00001)
  - **NOTE**: `kode_pembayaran` is DIFFERENT from `id_pembayaran` - one is internal ID, other is invoice format
- `id_user` - Foreign key ke users
- `id_pendaftaran` - Foreign key ke pendaftaran
- `jenis_pembayaran` - ENUM('pendaftaran', 'uang_pangkal_spp')
- `nominal_asal` - INT - Nominal sebelum potongan
- `potongan` - INT - Diskon (default 0)
- `angka_unik` - INT - Kode unik pembayaran (100-999)
- `total_transfer` - INT - Total yang harus dibayar
- `bukti_transfer` - VARCHAR (NULLABLE) - Filename bukti upload
- `gelombang` - INT (1-3)
- `status_verifikasi` - ENUM('pending', 'disetujui', 'ditolak')
- `catatan_verifikasi` - TEXT (NULLABLE)
- `timestamps`

**Foreign Keys**:
- `id_user` → users.id_user (ON DELETE CASCADE)
- `id_pendaftaran` → pendaftaran.id_pendaftaran (ON DELETE CASCADE)

**Indexes**:
- `idx_pembayaran_user_jenis` ON `id_user`, `jenis_pembayaran`
- `idx_pembayaran_status` ON `status_verifikasi`
- `idx_pembayaran_gelombang` ON `gelombang`
- ON `id_pendaftaran`

**Relations**:
- Belongs To: users, pendaftaran

---

### 4. **biodata_siswa** (Student & Family Biographical Data)
**Purpose**: Menyimpan data lengkap siswa, orang tua, wali, dan berkas dokumen

**Primary Key**: `id_biodata` BIGINT AUTO_INCREMENT

**Columns** (70+ fields):

**Student Data**:
- `id_biodata` - Primary key
- `id_user` - Foreign key ke users
- `id_pendaftaran` - Foreign key ke pendaftaran
- `nama_anak` - Nama lengkap anak
- `tempat_lahir`, `tgl_lahir`, `tgl_lahir_panjang`
- `jenis_kelamin` - ENUM('Laki-laki', 'Perempuan')
- `agama`, `nik`, `no_kk`, `golongan_darah`
- `anak_ke`, `jumlah_saudara`
- `tinggal_dengan`, `tinggal_dengan_lainnya`
- `alamat`, `alamat_ktp`, `alamat_tinggal`
- `tinggi_badan`, `berat_badan`
- `hobi`, `cita_cita`, `riwayat_penyakit`
- `transportasi`, `jarak_ke_sekolah`
- `nama_sekolah_asal`, `sekolah_asal_jenis`, `nipsn_nss_nsm`
- `lulusan_ra_yapiri` - BOOLEAN (untuk diskon)

**Father Data**: `nama_ayah`, `tempat_lahir_ayah`, `tgl_lahir_ayah`, `nik_ayah`, `no_kk_ayah`, `pekerjaan_ayah`, `pendidikan_ayah`, `alamat_ktp_ayah`, `alamat_tinggal_ayah`, `status_rumah_ayah`, `telp_ayah`, `email_ayah`, `penghasilan_ayah`

**Mother Data**: `nama_ibu`, `tempat_lahir_ibu`, `tgl_lahir_ibu`, `nik_ibu`, `no_kk_ibu`, `pekerjaan_ibu`, `pendidikan_ibu`, `alamat_ktp_ibu`, `alamat_tinggal_ibu`, `status_rumah_ibu`, `telp_ibu`, `email_ibu`, `penghasilan_ibu`, `no_hp_ortu`

**Guardian Data** (NULLABLE): `nama_wali`, `hubungan_wali`, `tempat_lahir_wali`, `tgl_lahir_wali`, `nik_wali`, `no_kk_wali`, `pekerjaan_wali`, `pendidikan_wali`, `alamat_wali`, `alamat_ktp_wali`, `alamat_tinggal_wali`, `status_rumah_wali`, `telp_wali`, `no_hp_wali`, `email_wali`, `penghasilan_wali`

**Document Files**:
- `file_pas_photo` - Pas foto
- `file_akte` - Akta kelahiran
- `file_ktp_ortu` - KTP orang tua
- `file_kk` - Kartu keluarga
- `file_ijazah_tk` - Ijazah TK (NULLABLE)
- `file_surat_ortu` - Surat pernyataan orang tua
- `file_surat_siswa` - Surat pernyataan siswa
- `file_surat_pernyataan_bermaterai` - Surat pernyataan bermaterai

**Document Status**:
- `status_surat_pernyataan` - Status upload surat pernyataan
- `status_berkas` - ENUM('belum_dicek', 'valid', 'tidak_valid')
- `catatan_berkas` - Catatan dari admin
- `tgl_verifikasi_berkas` - Timestamp verifikasi
- `diverifikasi_oleh` - Nama admin yang verifikasi
- `timestamps`

**Foreign Keys**:
- `id_user` → users.id_user (ON DELETE CASCADE)
- `id_pendaftaran` → pendaftaran.id_pendaftaran (ON DELETE CASCADE)

**Indexes**:
- `idx_biodata_user_status` ON `id_user`, `status_berkas`
- `idx_biodata_status_berkas` ON `status_berkas`
- ON `id_pendaftaran`

**Relations**:
- Belongs To: users, pendaftaran

---

### 5. **bakat_anak** (Online Talent Assessment)
**Purpose**: Menyimpan hasil tes bakat online

**Primary Key**: `id_bakat` BIGINT AUTO_INCREMENT

**Columns**:
- `id_bakat` - Primary key
- `id_user` - Foreign key ke users
- `id_pendaftaran` - Foreign key ke pendaftaran
- `skor_visual` - INT (default 0)
- `skor_logis` - INT (default 0)
- `skor_kreativitas` - INT (default 0)
- `skor_sosial_emosional` - INT (default 0)
- `skor_motorik` - INT (default 0)
- `total_waktu_detik` - INT - Durasi pengerjaan tes
- `kategori_dominan` - VARCHAR - Bakat paling menonjol
- `rekomendasi_parenting` - TEXT - Saran untuk orang tua
- `timestamps`

**Foreign Keys**:
- `id_user` → users.id_user (ON DELETE CASCADE)
- `id_pendaftaran` → pendaftaran.id_pendaftaran (ON DELETE CASCADE)

**Indexes**:
- `idx_bakat_user` ON `id_user`
- `idx_bakat_kategori` ON `kategori_dominan`
- ON `id_pendaftaran`

**Relations**:
- Belongs To: users, pendaftaran

---

### 6. **talenta_anak** (Parenting Management Talent)
**Purpose**: Menyimpan hasil tes parenting management

**Primary Key**: `id_talenta` VARCHAR(20) - Alphanumeric

**Columns**:
- `id_talenta` - Primary key, alphanumeric (Format: [User prefix]T[counter], e.g., HUST001)
- `id_user` - Foreign key ke users
- `id_pendaftaran` - Foreign key ke pendaftaran
- `skor_tes_anak` - INT
- `skor_kuesioner_ortu` - INT
- `kategori_hasil` - VARCHAR
- `rekomendasi_parenting` - TEXT
- `timestamps`

**Foreign Keys**:
- `id_user` → users.id_user (ON DELETE CASCADE)
- `id_pendaftaran` → pendaftaran.id_pendaftaran (ON DELETE CASCADE)

**Indexes**:
- ON `id_pendaftaran`

**Relations**:
- Belongs To: users, pendaftaran

---

### 7. **jadwal_interaksi** (Offline Test & Verification Schedule)
**Purpose**: Menyimpan jadwal tes offline dan sesi verifikasi

**Primary Key**: `id_jadwal` BIGINT AUTO_INCREMENT

**Columns**:
- `id_jadwal` - Primary key
- `id_user` - Foreign key ke users
- `id_pendaftaran` - Foreign key ke pendaftaran
- `tgl_tes_offline` - DATE (NULLABLE)
- `jam_tes_offline` - VARCHAR (NULLABLE)
- `nilai_tes_offline` - INT (NULLABLE)
- `status_kehadiran_offline` - VARCHAR (default 'Belum Hadir')
- `slot_verifikasi_bersama` - DATETIME (NULLABLE)
- `catatan_sesi_guru` - TEXT (NULLABLE)
- `tanda_tangan_digital` - TEXT (NULLABLE)
- `timestamps`

**Foreign Keys**:
- `id_user` → users.id_user (ON DELETE CASCADE)
- `id_pendaftaran` → pendaftaran.id_pendaftaran (ON DELETE CASCADE)

**Indexes**:
- `idx_jadwal_user_tanggal` ON `id_user`, `tgl_tes_offline`
- `idx_jadwal_kehadiran` ON `status_kehadiran_offline`
- ON `id_pendaftaran`

**Relations**:
- Belongs To: users, pendaftaran

---

## Supporting Tables

### 8. **pengumuman** (Announcements)
**Purpose**: Pengumuman untuk pendaftar/publik

**Primary Key**: `id_pengumuman` BIGINT AUTO_INCREMENT

**Columns**:
- `id_pengumuman` - Primary key
- `judul` - VARCHAR
- `konten` - TEXT
- `kategori` - ENUM('info', 'penting', 'pengumuman', 'jadwal')
- `is_aktif` - BOOLEAN (default TRUE)
- `is_publik` - BOOLEAN (default TRUE)
- `tanggal_mulai` - TIMESTAMP (NULLABLE)
- `tanggal_selesai` - TIMESTAMP (NULLABLE)
- `dibuat_oleh` - VARCHAR(20) - Foreign key ke users (admin)
- `timestamps`

**Foreign Keys**:
- `dibuat_oleh` → users.id_user (ON DELETE CASCADE)

**Indexes**:
- ON `dibuat_oleh`

**Relations**:
- Belongs To: users (admin)

---

### 9. **tahapan_ppdb** (PPDB Phase Management)
**Purpose**: Konfigurasi tahapan PPDB

**Primary Key**: `id_tahapan` BIGINT AUTO_INCREMENT

**Columns**:
- `id_tahapan` - Primary key
- `nama_tahapan` - VARCHAR
- `slug` - VARCHAR (UNIQUE)
- `deskripsi` - TEXT (NULLABLE)
- `urutan` - INT (default 0)
- `status` - ENUM('belum_mulai', 'berlangsung', 'selesai')
- `tanggal_mulai` - TIMESTAMP (NULLABLE)
- `tanggal_selesai` - TIMESTAMP (NULLABLE)
- `is_aktif` - BOOLEAN (default TRUE)
- `timestamps`

**Foreign Keys**: None (configuration table)

**Relations**: None

---

## Data Flow Diagram

```
┌─────────────────┐
│     USERS       │ (Pendaftar/Admin)
│  (id_user PK)   │
└────────┬────────┘
         │
         │ 1:N
         ▼
┌─────────────────┐
│  PENDAFTARAN    │ (Registration records per tahun ajaran)
│(id_pendaftaran) │
│   - gelombang   │
│   - tahap_aktif │
│   - status      │
└────────┬────────┘
         │
         ├──────────┐
         │ 1:N      │ 1:1
         ▼          ▼
┌────────────┐  ┌────────────────┐
│ PEMBAYARAN │  │ BIODATA_SISWA  │ (Student & Family data)
│  - pendaf. │  │   - siswa      │
│  - uang p. │  │   - ayah       │
│  - INV/..  │  │   - ibu        │
└────────────┘  │   - wali       │
                │   - files      │
                └────────────────┘
         │
         ├────────────────┬────────────────────┐
         │ 1:1            │ 1:1                │ 1:1
         ▼                ▼                    ▼
┌────────────────┐ ┌──────────────┐  ┌─────────────────┐
│  BAKAT_ANAK    │ │ TALENTA_ANAK │  │ JADWAL_INTERAKSI│
│ (Tes Online)   │ │ (Parenting)  │  │ (Tes Offline)   │
│  - 5 kategori  │ │  - skor      │  │  - jadwal       │
│  - rekomendasi │ │  - kategori  │  │  - nilai        │
└────────────────┘ └──────────────┘  └─────────────────┘
```

---

## 11 Tahapan Pendaftaran (status_langkah)

| Tahap | Status Langkah            | Deskripsi                           |
|-------|---------------------------|-------------------------------------|
| 1     | bayar_pendaftaran         | Upload bukti bayar pendaftaran      |
| 2     | verifikasi_pendaftaran    | Menunggu admin verifikasi pembayaran|
| 3     | isi_formulir              | Mengisi formulir biodata lengkap    |
| 4     | upload_berkas             | (Legacy - merged ke isi_formulir)   |
| 5     | bayar_uang_pangkal        | Upload bukti bayar uang pangkal     |
| 6     | verifikasi_uang_pangkal   | Menunggu admin verifikasi pembayaran|
| 7     | tes_online                | Tes bakat anak online               |
| 8     | tes_offline               | Tes offline di sekolah              |
| 9     | sesi_verifikasi           | Sesi verifikasi dengan guru         |
| 10    | verifikasi_berkas         | Admin verifikasi kelengkapan berkas |
| 11    | selesai                   | Pendaftaran selesai                 |

---

## Status Pendaftaran (status_pendaftaran)

| Status                  | Deskripsi                                |
|-------------------------|------------------------------------------|
| draft                   | Baru registrasi, belum bayar             |
| pembayaran_pending      | Bukti pembayaran pendaftaran diupload    |
| pembayaran_verified     | Pembayaran pendaftaran disetujui         |
| formulir_lengkap        | Formulir biodata sudah diisi             |
| berkas_pending          | Berkas dokumen sudah diupload            |
| berkas_verified         | Berkas dokumen sudah diverifikasi        |
| uang_pangkal_pending    | Bukti uang pangkal diupload              |
| uang_pangkal_verified   | Pembayaran uang pangkal disetujui        |
| tes_selesai             | Semua tes (online + offline) selesai     |
| diterima                | Diterima sebagai siswa baru              |
| ditolak                 | Tidak diterima                           |
| daftar_tunggu           | Masuk daftar tunggu                      |
| dibatalkan              | Pendaftaran dibatalkan                   |

---

## Key Features

### 1. **Referential Integrity**
- Semua foreign key menggunakan ON DELETE CASCADE
- Data akan otomatis terhapus saat user dihapus
- Tidak ada orphan records

### 2. **Performance Optimization**
- Index pada kolom yang sering di-query
- Index pada foreign keys
- Index pada kolom untuk filtering dan sorting

### 3. **Alphanumeric IDs**
- users: `HUS00626` (3 huruf nama + nomor urut + tahun)
- pendaftaran: `HUSR001` (3 huruf + R + nomor)
- pembayaran: `INV/2026/06/00001` (format invoice profesional)

### 4. **Data Audit Trail**
- Timestamps pada semua tabel
- Tracking verifikasi (diverifikasi_oleh, tgl_verifikasi)
- Catatan admin pada verifikasi

### 5. **Flexible Relations**
- User bisa punya multiple pendaftaran (tahun ajaran berbeda)
- Pendaftaran punya multiple pembayaran (pendaftaran + uang pangkal)
- Eager loading support untuk performance

---

## Migration History

| Date       | Migration                                      | Purpose                           |
|------------|------------------------------------------------|-----------------------------------|
| 2026-06-16 | create_pendaftaran_table_fixed                 | Membuat tabel pendaftaran         |
| 2026-06-16 | add_foreign_key_talenta_anak_id_user          | Re-add FK yang hilang             |
| 2026-06-16 | add_id_pendaftaran_to_related_tables          | Link semua tabel ke pendaftaran   |
| 2026-06-16 | migrate_existing_data_to_pendaftaran          | Migrate data existing             |
| 2026-06-16 | add_performance_indexes                        | Optimization indexes              |
| 2026-06-16 | add_foreign_key_pengumuman_dibuat_oleh        | Link pengumuman ke admin          |

---

## Best Practices

### Query Data
✅ **DO**: Use Eloquent relationships with eager loading
```php
$user = User::with(['pendaftaranAktif.pembayaran', 'pendaftaranAktif.biodataSiswa'])->find($id);
```

❌ **DON'T**: Query database multiple times (N+1 problem)
```php
$user = User::find($id);
$biodata = DB::table('biodata_siswa')->where('id_user', $id)->first();
```

### Insert Data
✅ **DO**: Always insert pendaftaran first, then related data with id_pendaftaran
```php
$pendaftaran = Pendaftaran::create([...]);
Pembayaran::create(['id_pendaftaran' => $pendaftaran->id_pendaftaran, ...]);
```

❌ **DON'T**: Insert related data without pendaftaran link
```php
Pembayaran::create(['id_user' => $user->id_user, ...]); // Missing id_pendaftaran
```

### Delete Data
✅ **DO**: Delete from parent (CASCADE will handle children)
```php
$user->delete(); // All pendaftaran, pembayaran, biodata, etc. auto-deleted
```

❌ **DON'T**: Manually delete child records
```php
DB::table('pembayaran')->where('id_user', $id)->delete();
DB::table('biodata_siswa')->where('id_user', $id)->delete();
// ... tedious and error-prone
```

---

## Audit Results (16 Juni 2026)

✅ **FIXED**:
- Tabel pendaftaran created
- Foreign key talenta_anak.id_user re-added
- Foreign key pengumuman.dibuat_oleh added
- All related tables linked to pendaftaran
- Performance indexes added
- 2 orphan pembayaran records cleaned
- Data integrity verified

✅ **VERIFIED**:
- 0 orphan records
- All foreign keys working
- Cascade deletes working
- Indexes created successfully

---

## Contact
For questions about database structure, contact system administrator or refer to this documentation.

**Last Updated**: 16 Juni 2026
**Version**: 2.0
