# SANGGRALOKA ONE — Database

Skema MySQL 8 / MariaDB untuk **satu aplikasi tiga modul**: **FIX**
(maintenance, HotelFix), **CREW** (SDM, HotelCrew), **SELL** (Sales &
Marketing, HotelSell). Satu database, prefix tabel per modul (`hr_`, `mkt_`,
tanpa prefix = FIX/inti).
Sumber kebenaran: `hotelfix.2/docs/SPEK-HOTELFIX-MASTER.md` (§5, modul FIX),
`hotelcrew/docs/SPEK-HOTELCREW-MASTER.md` (§4, modul CREW),
`hotelsell/docs/SPEK-HOTELSELL-V1-SEDERHANA.md` (§1–§7, modul SELL),
`sanggraloka-one/docs/PM-PLAN-SANGGRALOKA-ONE.md` (§4, kontrak pembagian migrasi).

## Cara menjalankan

Jalankan berurutan pada database kosong (nama database dibuat sendiri,
tidak dibuat oleh skrip ini):

```bash
mysql -u root -p namadb < migrations/0001_init.sql
mysql -u root -p namadb < migrations/0002_seed_master.sql
mysql -u root -p namadb < migrations/0003_seed_checklists.sql
mysql -u root -p namadb < migrations/0004_seed_demo.sql   # DEV-ONLY — jangan di produksi
mysql -u root -p namadb < migrations/0005_seed_expand.sql # DEV-ONLY — jangan di produksi
mysql -u root -p namadb < migrations/0006_seed_real_users.sql
mysql -u root -p namadb < migrations/0007_password_policy.sql
mysql -u root -p namadb < migrations/0008_hr_init.sql       # modul CREW — skema hr_*
mysql -u root -p namadb < migrations/0009_mkt_init.sql      # modul SELL — skema mkt_*
mysql -u root -p namadb < migrations/0010_seed_hr_hk.sql    # seed CREW: contoh divisi HK + GEN
mysql -u root -p namadb < migrations/0011_seed_mkt.sql      # seed SELL: 21 experiences, venue, mitra contoh
mysql -u root -p namadb < migrations/0012_sell_theme_followups.sql
mysql -u root -p namadb < migrations/0013_broadcast_app_source.sql
mysql -u root -p namadb < migrations/0014_crew_sell_role_enum_seed.sql   # DEV-ONLY (10 user QC) — jangan di produksi
mysql -u root -p namadb < migrations/0015_role_assignment.sql           # DRAFT role 92 karyawan nyata
mysql -u root -p namadb < migrations/0016_seed_dummy_demo.sql           # DEV-ONLY — jangan di produksi
```

Atau via client lain (phpMyAdmin, DBeaver, Adminer, TablePlus) — impor
ketiga file dalam urutan yang sama, satu per satu.

**Wajib berurutan**: 0002 dan 0003 melakukan INSERT dengan `id` eksplisit
yang mengasumsikan tabel kosong dan mereferensikan baris dari migrasi
sebelumnya (mis. `assets.category_id` dari 0002 mengacu ke
`asset_categories` yang di-seed di file yang sama, dan `pm_schedules`/`work_orders`
masa depan akan mengacu ke `checklists` dari 0003).

## Konvensi penomoran file

- Format: `NNNN_deskripsi_singkat.sql`, empat digit, increment 1.
- `0001` selalu skema (DDL) inti — tidak ada data.
- `0002+` adalah seed/data atau perubahan skema berikutnya, urut secara
  kronologis sesuai kapan migrasi ditulis (bukan diacak/di-reorder).
- Setiap migrasi baru menambah file baru — **jangan mengedit file migrasi
  yang sudah pernah dijalankan di environment manapun** (staging/prod);
  perubahan struktur setelah itu wajib file baru (mis. `0005_alter_x.sql`).
- **`0004_seed_demo.sql` dan `0005_seed_expand.sql` adalah seed demo
  DEV-ONLY** — dummy data transaksional (work orders, biaya, notifikasi,
  meter, broadcast) plus staf/lokasi/aset demo tambahan untuk QC dashboard.
  Tanggal-tanggalnya relatif terhadap `NOW()` sehingga Rekap Harian selalu
  berisi. **Jangan pernah dijalankan di produksi**; di produksi lompat dari
  0003 langsung ke migrasi berikutnya.
- Migrasi harus idempoten secara urutan (boleh diasumsikan hanya dijalankan
  sekali secara berurutan pada database yang belum pernah menjalankannya),
  bukan idempoten by re-run (tidak ada `IF NOT EXISTS` pada DDL, sengaja,
  agar kesalahan skema cepat ketahuan).

## Daftar tabel per kelompok (§5 spesifikasi)

### Master (13 tabel)
`properties`, `users`, `user_property_access`, `push_tokens`, `skills`,
`locations`, `asset_categories`, `assets`, `asset_documents`, `vendors`,
`contracts`, `contract_assets`, `parts`

### Checklist (2 tabel)
`checklists` (versioning: `version`, `status` draft/active/archived,
`parent_version_id`), `checklist_items`

### Jadwal & meter (3 tabel)
`pm_schedules`, `meter_readings`, `generator_log`

### Pekerjaan (5 tabel)
`work_orders`, `wo_checklist_results`, `wo_parts`, `photos`, `history`
(immutable — tanpa `updated_at`)

### Biaya (1 tabel)
`wo_costs`

### Utilitas (2 tabel)
`utility_meters`, `utility_readings`

### Notifikasi (3 tabel)
`notifications`, `broadcasts`, `broadcast_receipts`

**Total: 29 tabel** (ditambah 1 tabel penghubung many-to-many
`contract_assets` yang termasuk dalam grup Master di atas → total baris
CREATE TABLE = 29, sesuai daftar §5 lengkap termasuk relasi).

## Modul CREW (`0008_hr_init.sql` + `0010_seed_hr_hk.sql`)

Semua tabel prefix `hr_`, ber-`property_id`, sesuai §4
`SPEK-HOTELCREW-MASTER.md`. **46 tabel hr_ + `audit_logs` + `auth_stepup`**
(append-only kedua modul) + ALTER `users` (`employee_id`, `is_trainer`).

- **Master & dokumen (F1):** `hr_positions`, `hr_employees`, `hr_documents`,
  `hr_contracts`
- **Absensi/shift/roster/cuti (F2–F4):** `hr_attendance_logs` (immutable),
  `hr_shifts`, `hr_attendance_days`, `hr_roster`, `hr_leave_types`,
  `hr_leaves`, `hr_shift_swaps`
- **University & kompetensi (F5–F7):** `hr_faculties`, `hr_competencies`,
  `hr_employee_comp`, `hr_courses`, `hr_tests`, `hr_test_attempts`
- **Ops harian — MOD, guest cards, briefing, task, AI intake (F15–F16):**
  `hr_mod_roster`, `hr_guest_imports`, `hr_guest_cards`, `hr_briefings`,
  `hr_briefing_acks`, `hr_briefing_items`, **`hr_tasks`** (papan task
  terpadu — kolom kontrak `source_app` ENUM('crew','sell') + `source_ref`,
  dipakai juga oleh modul SELL), `hr_ai_intakes`
- **Challenge, KPI, inspeksi, disiplin (F8–F9):** `hr_challenges`,
  `hr_challenge_results`, `hr_kpi_definitions`, `hr_kpi_scores`,
  `hr_inspections`, `hr_discipline`
- **Payroll, service charge, kasbon — zona merah (F10):**
  `hr_payroll_periods`, `hr_payroll_items` (earnings/deductions/
  employer_costs/gross/net/pph21 = `TEXT` ciphertext, dienkripsi level
  aplikasi), `hr_payroll_params` (tarif bertanggal-berlaku),
  `hr_service_charge`, `hr_loans`
- **Rekrutmen, onboarding, DW, trainee (F11–F12):** `hr_vacancies`,
  `hr_candidates`, `hr_onboarding`, `hr_dw_pool`, `hr_trainees`
- **Handover, insiden, lost&found, grievance, exit (F13–F14):**
  `hr_handover_logs`, `hr_incidents`, `hr_lost_found`, `hr_grievances`,
  `hr_exit` (`hr_announcements` TIDAK dibuat — memakai `broadcasts`
  existing dgn `app_source='hrd'`, sesuai §4 spek)
- **Audit & step-up:** `audit_logs` (append-only, `module` ENUM('fix','crew'),
  dipakai kedua modul), `auth_stepup` (PIN 6 digit + OTP WA, zona merah payroll)

Seed `0010_seed_hr_hk.sql` (HANYA divisi Housekeeping + faculty GEN, sesuai
PM-PLAN §1 butir 5): `hr_shifts` (M/A/N/M2/OFF/PH), `hr_leave_types` (5 jenis),
`hr_faculties` (General Studies + Housekeeping), `hr_competencies` (kode
nyata HK-01/03/04/08/09/10/12/13/14/16/17/18/19/20, LDR-01/02/04/05/07,
GEN-18/20/21 + `GEN-CORE` placeholder eksplisit — lihat sumber per baris di
file migrasi), `hr_positions` HK (Room Attendant, Public Area Attendant,
Linen Attendant, HK Coordinator, Executive Housekeeper), 8 `hr_employees`
ter-link ke user housekeeping dari `0006_seed_real_users.sql`, `hr_roster`
1 minggu, 3 `hr_courses` + 1 `hr_tests` (5 soal), 6 `hr_kpi_definitions`
(dari `hotelcrew/docs/kpi/HK-scorecard.md`), `hr_payroll_params` default
(BPJS/JHT/JP/JKK/JKM/ot_divisor sesuai §4 spek F10; `umk` = 0, ditandai
`[ANGKA: tetapkan HR]`).

## Modul SELL (`0009_mkt_init.sql` + `0011_seed_mkt.sql`)

Semua tabel prefix `mkt_`, ber-`property_id`, sesuai §1–§7
`SPEK-HOTELSELL-V1-SEDERHANA.md`. **20 tabel mkt_.** SELL **tidak** membuat
sistem task/reminder sendiri — semua reminder (kontrak H-60/H-30, penawaran
3/5/7, KOL H-7/H-1/H+7) ditulis sbg baris `hr_tasks` dgn `source_app='sell'`.

- **M1 mitra & kontrak:** `mkt_partners`, `mkt_contracts`
- **M2 produk & booking:** `mkt_products`, `mkt_bookings`, `mkt_payments`
- **M3 kanban penawaran:** `mkt_quotations` (+ `last_status_change`,
  `lost_reason`, `pic_user_id` sesuai kontrak PM-PLAN §4), `mkt_quote_log`
  (immutable, append-only)
- **M4 KOL:** `mkt_kols`, `mkt_kol_visits`, `mkt_kol_docs`, `mkt_kol_results`
- **M5 Menu & Event Studio:** `mkt_event_ideas`, `mkt_menus`
- **M6 keyword & auto-generation:** `mkt_keywords`, `mkt_generated_content`
- **M7 AI chatbot studio:** `mkt_ai_chats`, `mkt_ai_messages`
- **Reservasi PMS & OTB (port dari `hotelsell/app/data/hotelsell_mysql.sql`):**
  `mkt_reservations`, `mkt_otb_snapshots` (dari `mkt_uploads` prototipe),
  `mkt_upload_maps` (dari `mkt_colmap` prototipe) — read-only dari sisi PMS,
  HotelSell tidak pernah menulis reservasi kamar.

Seed `0011_seed_mkt.sql`: 21 experiences (sanggraloka.md §2, kategori
`experience`) + venue tambahan (Anandam Wedding Chapel, Yoga Shala, Meditera
MICE) sbg `mkt_products` (harga publik/mitra `NULL` — diisi tim S&M), 3
`mkt_partners` GENERIK (nama "Contoh ..." — bukan mitra nyata). Master alasan
LOST sudah jadi ENUM `mkt_quotations.lost_reason` di `0009` (tidak ada tabel
master terpisah di kontrak §4 spek); daftar kompetitor Key set Ubud dicatat
bebas di `lost_reason_detail`.

## Penetapan role karyawan nyata (`0015_role_assignment.sql`)

Migrasi ini menetapkan `user_property_access.role` (ENUM final dari `0014`)
untuk seluruh **92 karyawan nyata** yang di-seed di `0006_seed_real_users.sql`,
berdasarkan posisi aktual per `docs/USERNAME-KARYAWAN-SANGGRALOKA.xlsx`
(kolom Jabatan) dipetakan ke aturan role PM-PLAN §2 + struktur organisasi
`sanggraloka.md` §4. **Ini DRAFT** — satu `UPDATE` per user (di-JOIN via
`username`), dikelompokkan per divisi (A&G, HRD+Security, FO, HK, F&B, ENG,
SPA, S&M) dengan komentar posisi di tiap baris, supaya HR bisa mengoreksi
baris per baris tanpa mengubah struktur file. Hasil: **gm 1 · fc 1 ·
hr_admin 1 · hr_staff 1 · sales_manager 1 · sales 3 · ewm 1 · design 1 ·
dept_head 5 · supervisor 10 · technician 17 · employee 50** (total 92; 70
baris berubah dari role lama `0006`, 22 tidak berubah). Tiga keputusan
dicatat eksplisit di komentar migrasi karena bukan pemetaan langsung:
(1) Human Capital Manager `property_admin`→`hr_admin` (dianggap placeholder
pra-CREW, dipetakan sesuai aturan meski menyimpang dari kaidah umum "jangan
ubah `property_admin` existing"); (2) Spa Coordinator & Security Coordinator
turun `dept_head`→`supervisor` mengikuti aturan literal "Level Coordinator",
dengan **gap akses FIX dicatat** (kehilangan `admin.php`/`parts.php`/
`vendors.php`/`asset-health.php`/`compliance.php`/`energy.php` yang dimiliki
`dept_head`); (3) IT Coordinator dan 3 Steward dipertahankan pada role lama
(`dept_head`/`technician`) dengan komentar `TODO HR` karena tidak eksplisit
tercakup aturan pemetaan. Diuji end-to-end di database sementara
`role_test_s1` (migrasi 0001→0015 berurutan, tanpa error) lalu di-drop.

## Data dummy demo/uji (`0016_seed_dummy_demo.sql`)

Migrasi **DEV-ONLY** (pola sama dgn `0004`/`0005`) yang mengisi data
operasional dummy supaya dashboard, papan task terpadu, kanban penawaran,
kalender, dan guest cards terlihat "hidup" saat demo/uji — bukan fakta
resort, bukan tamu/klien/karyawan nyata (penamaan eksplisit "Demo Guest",
"Demo Bride & Groom", "PT Demo Sejahtera", dst; `mkt_products.price` tetap
`NULL`, tidak ada kode kompetensi baru). Tanggal dibangun relatif thd
`CURDATE()`/`NOW()` (via `DATE_SUB`/`DATE_ADD`/`ADDTIME`) supaya selalu segar
kapan pun dijalankan. Referensi FK 100% memakai user/karyawan/produk yang
sudah ada di seed sebelumnya (tidak membuat master baru) — termasuk memakai
role S&M nyata dari `0015_role_assignment.sql` (520/521 sales, 562
sales_manager, 582 sales) dan sengaja **menghindari** user 544
(ketut.darmika) sbg PIC transaksi karena `0015` menetapkan role aktualnya
`design`, bukan sales rep.

Isi ringkas:
- **CREW**: `hr_attendance_days` 14 hari x 8 karyawan HK (hadir/telat/off/
  alpha + 3 baris `leave` sinkron dgn `hr_leaves`), `hr_attendance_logs` (8
  raw punch), `hr_leaves` (1 approved, 1 pending SPV, 1 sakit menunggu
  surat), `hr_roster` extend minggu depan (42 baris, 6 karyawan), 4
  `hr_tasks` `source_app='crew'` (open/overdue/in_progress/done), 2
  `hr_discipline` coaching (ack pending), 1 `hr_kpi_scores` periode berjalan
  dept Housekeeping, 1 `hr_briefings` hari ini + 3 `hr_briefing_items`, 1
  `hr_guest_imports` + 6 `hr_guest_cards` hari ini (2 arrival VIP KR/FR, 3
  inhouse, 1 departure), 2 `hr_test_attempts` lulus (proxy test HK-01/hr_tests
  id=1 — TIDAK ada test bernama literal "HK-L1" di seed, dicatat eksplisit).
- **SELL**: 2 `mkt_contracts` tambahan (1 H-60 dari expired, 1 aktif
  setahun), 5 `mkt_bookings` (wedding tentative+confirmed dgn deposit
  sebagian, 2 experience executed, 1 cancelled) + `mkt_payments`, 6
  `mkt_quotations` sebaran kanban (sent/follow_up/nego umur 8 hari/won/lost
  dgn `lost_reason`/expired) + `mkt_quote_log`, 2 `mkt_kols` (1 deal dgn
  visit H+10 & photo release belum upload, 1 delivered lengkap dokumen+hasil)
  + `mkt_kol_visits`/`mkt_kol_docs`/`mkt_kol_results`, 4 `hr_tasks`
  `source_app='sell'` (follow-up quotation, kontrak H-60, KOL H-7, review
  konten).
- **Lintas modul**: 7 `notifications` (task assigned, cuti pending SPV,
  kontrak H-60, quotation follow-up merah, KOL H-10, discipline ack pending,
  KPI terbit).
- **FIX**: sengaja TIDAK menambah `work_orders` — 0004/0005 sudah punya
  puluhan WO status reported/in_progress/waiting_parts/scheduled, papan
  task FIX tidak kosong.

Diuji end-to-end di database sementara `dummy_test_s1` (migrasi
0001→0016 berurutan, termasuk `0015_role_assignment.sql`, tanpa error;
COUNT tiap tabel sesuai; 0 baris FK yatim pada
assignee/pic_user_id/host_user_id/notifications.user_id) lalu di-drop.

## Dummy data lengkap seluruh tabel kosong (`0017_seed_dummy_full.sql`)

Migrasi **DEV-ONLY** susulan dari `0016` (permintaan owner: setiap halaman
punya isi) yang mengisi **39 tabel yang masih 0 baris** per inventaris
8 Jul 2026 — 20 tabel `hr_` (dokumen/kontrak/shift swap/MOD roster/
challenge+hasil/inspeksi/kasbon/vacancy+kandidat/onboarding/DW pool/
trainee/handover/insiden/lost&found/grievance/exit/AI intake/payroll
period+service charge/employee_comp/briefing ack), 12 tabel `mkt_`
(ai chat+message/event idea/menu/keyword/generated content/kol result
tambahan/reservasi+OTB snapshot/upload map), dan 5 tabel `fix`
(photos/wo_checklist_results/generator_log + audit_logs/auth_stepup di
grup Audit). Semua nama tamu/kandidat/insiden ditandai eksplisit
"(DUMMY demo)"; hanya mereferensikan FK yang sudah ada (tidak membuat
user/karyawan/produk/kode kompetensi baru); `mkt_products.price` tetap
`NULL`. **Dua tabel sengaja dibiarkan 0 baris** dengan alasan tercatat di
komentar migrasi: `hr_payroll_items` (kolom ciphertext earnings/
deductions/gross/net/pph21 — belum ada kelas enkripsi/dekripsi di
codebase dan `HrPayrollPage.php` eksplisit menyatakan nominal per-karyawan
"di luar scope v1"; mengisi teks biasa akan menyesatkan sbg ciphertext
palsu) dan `push_tokens` (butuh token FCM/APNs nyata dari device
terpasang, sama seperti keputusan `0016`). Diuji end-to-end di database
sementara `dummy_full_test` (migrasi 0001→0017 berurutan tanpa error,
COUNT tiap tabel diverifikasi) lalu di-drop, kemudian diterapkan ke
database live via `bin/migrate.php`.

## Aturan teknis yang diterapkan

- `ENGINE=InnoDB`, `CHARSET=utf8mb4`, `COLLATE=utf8mb4_unicode_ci` di semua
  tabel.
- Kolom enum memakai tipe `ENUM(...)` sesuai nilai persis di §5/§10.
- Semua tabel master & transaksi membawa `property_id` (langsung, atau via
  tabel induk untuk tabel anak seperti `checklist_items`, `wo_parts`, dst —
  lihat catatan di bawah).
- Index gabungan kunci sesuai spesifikasi:
  - `work_orders (property_id, status, due_date)`
  - `assets (property_id, category_id)`
  - `notifications (property_id, user_id, read_at)`
  - serta index gabungan `(property_id, ...)` lain pada `locations`,
    `pm_schedules`, `parts`, `utility_meters`, `checklists`, dll.
- `assets.accounting_asset_no` UNIQUE **per property** (bukan global) —
  `UNIQUE KEY uq_assets_property_accounting_no (property_id, accounting_asset_no)`,
  sesuai keputusan §13.6.
- `history` tidak memiliki `updated_at` (immutable, hanya `created_at`).
- Foreign key diberi nama konstrain eksplisit (`fk_<tabel>_<kolom/ref>`)
  agar mudah dibaca saat debugging error.
- `wo_checklist_results`, `wo_parts`, `photos`, `history`, `wo_costs`
  mewarisi isolasi properti secara transitif melalui `work_orders.property_id`
  (tidak mengulang kolom `property_id` di tabel anak — pola ini konsisten
  dengan §5 yang tidak mencantumkan `property_id` eksplisit pada tabel-tabel
  tersebut).

## Catatan / penyimpangan dari spesifikasi (baca sebelum pakai di produksi)

1. **Password seed** (`0002_seed_master.sql`): semua 6 user memakai hash
   bcrypt yang SAMA untuk kata `password`
   (`$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy` — hash
   publik yang umum dipakai sebagai contoh `password_hash('password', PASSWORD_BCRYPT)`
   pada dokumentasi PHP). **Wajib diganti** sebelum environment ini
   dipakai di luar development lokal.
2. **`asset_categories.default_checklist_id`** memiliki dependensi melingkar
   dengan `checklists.property_id -> properties` (checklist butuh kategori
   tidak, tapi kategori butuh checklist). Diselesaikan dengan menunda FK
   `asset_categories.default_checklist_id -> checklists(id)` memakai
   `ALTER TABLE` setelah `CREATE TABLE checklists` di 0001 — bukan
   penyimpangan data, hanya teknik DDL.
3. **`utility_readings.period_month`** disimpan sebagai `DATE` (tanggal 1
   bulan berjalan, mis. `2026-07-01`) karena §5 hanya menyebut
   `period_month` tanpa tipe eksplisit; alternatif `CHAR(7)` ('2026-07')
   dipertimbangkan tapi `DATE` dipilih agar mudah dipakai untuk range query
   dan sorting tanpa cast.
4. **`push_tokens`** tidak diberi `id` primary key komposit tapi `id`
   surrogate `BIGINT AUTO_INCREMENT` + `UNIQUE KEY` pada `token` — §5 tidak
   menyebutkan PK eksplisit untuk tabel ini.
5. **Seed checklist di 0003**: dari 32 checklist di §10, hanya **12** yang
   di-seed (sesuai instruksi — representatif, bukan pustaka lengkap). 9
   wajib disebutkan di brief + 3 pilihan tambahan (Cooling Tower mingguan,
   Panel Listrik & Trafo bulanan, Kendaraan bulanan+5000km). Pustaka
   lengkap 32 checklist disarankan disusulkan di migrasi berikutnya
   (`0004_seed_checklists_lengkap.sql`) sebelum go-live Fase 2.
6. **`skills`** untuk user selain Candra (teknisi) tidak di-seed — hanya
   representasi minimal sesuai matriks kompetensi §4.1 (Candra: certified
   Genset, advanced AC Split & Pompa). Dept head/admin/viewer tidak
   memerlukan baris `skills` untuk berfungsi normal.
7. **`generator_log`, `meter_readings`, `wo_*`, `notifications`,
   `broadcasts`** sengaja **tidak di-seed** — tabel transaksional ini
   dianggap kosong di awal (akan terisi natural saat aplikasi berjalan/API
   dipanggil), sesuai cakupan "seed minimal untuk development" pada brief.
8. **Kolom `assets.meter_current`** dan `pm_schedules.next_due_meter`
   memakai `DECIMAL(14,2)` (bukan `INT`) agar mendukung meter berbasis jam
   desimal (mis. running hour genset `4820.50`) maupun km bulat — desain
   generik untuk `meter_type` apa pun.
9. **`hr_positions`/`hr_faculties`/`hr_competencies`/`hr_kpi_definitions`
   dll (0008)** ditambah `property_id` eksplisit meski §4 SPEK-HOTELCREW
   tidak selalu menuliskannya — konsisten dgn aturan "semua tabel
   ber-`property_id`" (PM-PLAN §1) dan pola tabel FIX di 0001.
10. **Kolom polimorfik tanpa FK tunggal** (`hr_briefing_items.ref_id`,
    `hr_test_attempts.candidate_id`, `hr_challenge_results.participant_id`
    + `team`): mengacu >1 kemungkinan tabel target, sesuai definisi §4
    spek ("user_id/candidate_id", "participant_id/team") — FK hanya
    dipasang pada satu sisi yang jelas (`hr_test_attempts.candidate_id ->
    hr_candidates` ditambahkan via ALTER susulan setelah `hr_candidates`
    dibuat).
11. **Kolom gaji ciphertext** (`hr_payroll_items.earnings/deductions/
    employer_costs/gross/net/pph21`, `hr_employees.bank_account`) bertipe
    `TEXT` (bukan `DECIMAL`/`VARCHAR`) sesuai instruksi eksplisit — enkripsi
    level aplikasi, kunci TIDAK disimpan di database (§3.2 spek).
12. **`GEN-CORE`** (0010) adalah kode PLACEHOLDER eksplisit, bukan nomor
    kompetensi final — jobdesk HK hanya menulis "GEN core Lv2 (semua)"
    tanpa nomor pasti; nomor sebenarnya menyusul saat
    `KOMPETENSI-UNIVERSITY.xlsx` (232 kompetensi) diimpor penuh.
13. **Pemetaan 8 `hr_employees` HK ke sub-posisi** (Room Attendant/Public
    Area Attendant/Linen Attendant/HK Coordinator/Executive Housekeeper)
    di `0010_seed_hr_hk.sql` adalah ASUMSI SEED — data
    `0006_seed_real_users.sql` hanya membawa department+role FIX, tidak
    membedakan sub-posisi HK. **Perlu konfirmasi HR** sebelum dipakai
    sbg data karyawan riil, bukan sekadar contoh migrasi.
14. **`hr_payroll_params.umk`** diisi `0` dengan komentar eksplisit
    `[ANGKA: tetapkan HR]` — tidak ada angka UMK Gianyar resmi di
    dokumen spek/sanggraloka.md, sesuai larangan mengarang angka standar.
15. **`mkt_products`** (0011) menambah 2 baris venue di luar 21
    experiences resmi (Yoga Shala Session, MICE/Private Dining Meditera)
    untuk memenuhi instruksi "venue (Anandam, shala, farm, Meditera)" —
    ditandai eksplisit di komentar migrasi agar tidak tercampur dgn
    daftar 21 experiences resmi `sanggraloka.md` §2 saat verifikasi silang.
16. **3 `mkt_partners` seed** memakai nama "Contoh Travel Agent A/Contoh
    OTA B/Contoh Wedding Organizer C" dgn kontak dummy (`+628000000xx`,
    `@example.com`) — generik sesuai instruksi, BUKAN nama mitra nyata.
