init push
This commit is contained in:
74
docs/duty-roster-migrations.md
Normal file
74
docs/duty-roster-migrations.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Duty Roster Migrations
|
||||
|
||||
Catatan eksekusi 6 file migrasi duty roster di [`migrations/`](../migrations/).
|
||||
|
||||
## Konfigurasi Tool Migrasi
|
||||
|
||||
Tool migrasi yang dipakai project ini adalah `goose` ([Makefile:5](../Makefile#L5)) dengan direktori target:
|
||||
|
||||
```makefile
|
||||
GOOSE_DIR ?= migrations/goose
|
||||
```
|
||||
|
||||
**Konsekuensi:** `make migrate-up` hanya menjalankan file di [`migrations/goose/`](../migrations/goose/). Semua file `.sql` di root [`migrations/`](../migrations/) — termasuk 6 file duty roster — **tidak otomatis dieksekusi**. Mereka berfungsi sebagai script SQL ad-hoc / log historis dan harus dijalankan manual (`mysql < file.sql`) atau dipindahkan ke `migrations/goose/` dengan format `-- +goose Up` / `-- +goose Down`.
|
||||
|
||||
## Urutan Eksekusi (by Timestamp)
|
||||
|
||||
Goose mengurutkan file by filename ascending. Untuk dua file dengan timestamp sama (`20260502`), urutannya alfabetis: `duty` < `purge`.
|
||||
|
||||
```
|
||||
1. 20260408_hems_duty_roster_crews_confirm_at.sql
|
||||
2. 20260429_rename_duty_roster_tables.sql
|
||||
3. 20260430_normalize_duty_roster_legacy.sql
|
||||
4. 20260502_duty_roster_permissions_unification.sql
|
||||
5. 20260502_purge_legacy_hems_duty_roster_permissions.sql
|
||||
6. 20260504_duty_roster_drop_unique_base_date.sql
|
||||
```
|
||||
|
||||
## Efek per File
|
||||
|
||||
| # | File | Efek di DB |
|
||||
|---|---|---|
|
||||
| 1 | [`20260408_hems_duty_roster_crews_confirm_at.sql`](../migrations/20260408_hems_duty_roster_crews_confirm_at.sql) | `ALTER TABLE hems_duty_roster_crews ADD COLUMN confirm_at DATETIME NULL` (nama tabel lama). Idempoten via `IF NOT EXISTS`. |
|
||||
| 2 | [`20260429_rename_duty_roster_tables.sql`](../migrations/20260429_rename_duty_roster_tables.sql) | `RENAME TABLE hems_duty_rosters → duty_rosters` dan `hems_duty_roster_crews → duty_roster_crews`. Pakai guard cek `information_schema.TABLES` jadi idempoten. |
|
||||
| 3 | [`20260430_normalize_duty_roster_legacy.sql`](../migrations/20260430_normalize_duty_roster_legacy.sql) | (a) Rename tabel **lagi** dengan guard sama (no-op kalau #2 sukses). (b) Rename kolom `hems_base_id → base_id` di `duty_rosters`. (c) Ensure `confirm_at` ada di `duty_roster_crews` baru (dan di tabel lama jika masih ada). (d) Rewire FK `roster_crews.roster_id` & `duty_roster_other_persons.roster_id` supaya reference `duty_rosters` (bukan `hems_duty_rosters`). |
|
||||
| 4 | [`20260502_duty_roster_permissions_unification.sql`](../migrations/20260502_duty_roster_permissions_unification.sql) | Insert 4 permission canonical: `duty_roster.{create,read,update,delete}` (skip via `WHERE NOT EXISTS`). Grant ke role yang punya legacy `hems_duty_roster.*` ke canonical baru, anti-duplikasi via `LEFT JOIN ... NOT EXISTS`. **Tidak menghapus legacy.** |
|
||||
| 5 | [`20260502_purge_legacy_hems_duty_roster_permissions.sql`](../migrations/20260502_purge_legacy_hems_duty_roster_permissions.sql) | (a) Re-insert canonical (no-op jika #4 sudah jalan). (b) Re-grant (no-op). (c) `DELETE FROM role_permissions WHERE permission.key IN (legacy)`. (d) `DELETE FROM permissions WHERE key IN (legacy)`. |
|
||||
| 6 | [`20260504_duty_roster_drop_unique_base_date.sql`](../migrations/20260504_duty_roster_drop_unique_base_date.sql) | `DROP INDEX idx_roster_base_date` (sebelumnya unique). `CREATE INDEX idx_roster_base_date ON duty_rosters(base_id, duty_date)` non-unique. **Konsekuensi aplikasi**: schema tidak lagi mencegah multiple roster di pasangan `(base_id, duty_date)` yang sama — kode handler harus mendukung skenario ini (lihat [`ensureRosterDates`](../internal/transport/http/handlers/duty_roster_handler.go) yang sebelumnya dedup-by-date). |
|
||||
|
||||
## Overlap & Redundansi
|
||||
|
||||
- **#2 ⊂ #3** — File `0430_normalize_legacy` sudah include semua rename dari `0429` dengan guard yang sama. File `0429` secara fungsional redundant; akan no-op jika dijalankan setelah `0430`.
|
||||
- **#4 ⊂ #5** — File `0502_purge_legacy` adalah superset dari `0502_unification`. Insert + grant identik, lalu tambah `DELETE`. File `0502_unification` redundant.
|
||||
|
||||
## Idempotensi Re-run
|
||||
|
||||
| File | Idempoten? | Catatan |
|
||||
|---|---|---|
|
||||
| #1 | Ya | `ADD COLUMN IF NOT EXISTS` |
|
||||
| #2 | Ya | Guard via `information_schema.TABLES` |
|
||||
| #3 | Ya | Setiap `ALTER` dibungkus prepared statement dengan kondisi |
|
||||
| #4 | Ya | `WHERE NOT EXISTS` untuk insert + `LEFT JOIN ... IS NULL` untuk grant |
|
||||
| #5 | Ya | Sama dengan #4, ditambah `DELETE` (idempoten karena hanya hapus key legacy) |
|
||||
| **#6** | **TIDAK** | `DROP INDEX idx_roster_base_date` tanpa guard. Re-run setelah index sudah di-drop + recreate (sebagai non-unique) akan **gagal** karena nama indexnya sama. Mestinya pakai prepared statement guard yang cek `information_schema.STATISTICS`. |
|
||||
|
||||
## Versi Minimal yang Setara
|
||||
|
||||
Dengan urutan dijalankan penuh, efek akhirnya cukup dicapai dengan 4 file:
|
||||
|
||||
```
|
||||
0408_..._confirm_at (kompat untuk env yang masih punya hems_duty_*)
|
||||
0430_normalize_legacy (rename tabel + kolom + FK + confirm_at — superset dari 0429)
|
||||
0502_purge_legacy (canonical perm + delete legacy — superset dari 0502_unification)
|
||||
0504_drop_unique_base_date (butuh tambah guard agar idempoten)
|
||||
```
|
||||
|
||||
Untuk env yang sudah pasti **fresh** (tanpa sisa `hems_duty_*` apa pun), kelima file di atas bisa digabung jadi satu file goose-format dengan branch yang relevan saja. **Sebelum melakukan ini, pastikan tidak ada DB target yang masih nyangkut di mid-state** — file individual lebih aman selama transisi.
|
||||
|
||||
## Bug yang Pernah Ditemukan Akibat #6
|
||||
|
||||
Setelah `idx_roster_base_date` jadi non-unique, beberapa baris kode aplikasi yang asumsi unique (`base_id`, `duty_date`) jadi salah:
|
||||
|
||||
- [`ensureRosterDates`](../internal/transport/http/handlers/duty_roster_handler.go) di handler GET `/duty-roster/get-all` & `/get-all/dt` sebelumnya pakai `map[string]R` keyed by date — multiple roster di tanggal sama di-overwrite, hanya 1 yang muncul. Sudah diperbaiki pakai `map[string][]R` (multi-value).
|
||||
|
||||
Pastikan setiap kode baru yang query/iterasi roster per `(base_id, duty_date)` menyadari bahwa kombinasi ini tidak lagi unique.
|
||||
Reference in New Issue
Block a user