7. Proses Gaji¶
Bab ini menjelaskan alur eksekusi payroll per periode: review data input → setup pengecualian → jalankan Kalkulasi.
Prerequisite¶
Sebelum mulai, pastikan: - Periode lewat terakhir sudah di-Tutup Bulan (snapshot evaluasi tersedia) - Master Penggajian (Komponen Gaji + Formula + Fasilitas + Jenis Pinjaman) sudah lengkap (lihat 04-master-pendukung.md) - Setting Karyawan (gaji_pokok + status + bank) sudah di-isi untuk semua karyawan (lihat 02-master-organisasi.md)
Periode default: semua sub-menu Proses Gaji pakai periode lewat terakhir sebagai default fixed (tidak bisa diubah di UI).
Akses: sidebar Proses Gaji → 7 sub-menu.
7.1 Data Waktu Kerja¶
Akses: Proses Gaji → Data Waktu Kerja.
Apa yang ditampilkan¶
Read-only snapshot dari evaluasi periode lewat terakhir. 2 tab:
Tab "Jumlah Hari Kerja":
- Tabel: NIK | Karyawan | Jumlah Hari
- Sumber: evaluasi_kelebihan_hari_kerja.jumlah_hadir_aktual
- Hanya karyawan dengan record (abaikan yang 0)
Tab "Jumlah Lembur":
- Tabel: NIK | Karyawan | Jumlah Jam Lembur
- Sumber: evaluasi_akumulasi_lembur.total_durasi_menit ÷ 60
- Format: decimal 2 desimal (mis. "12,50 jam")
Tujuan¶
Verifikasi data input ke kalkulasi gaji. Bila ada data salah: - Hari kerja salah → re-trigger Tutup Bulan periode tsb - Lembur kurang → cek lembur DISETUJUI di periode tsb (mungkin ada yang masih PENDING)
Search¶
Search by NIK atau nama karyawan, debounced 250ms.
7.2 Data Fasilitas¶
Akses: Proses Gaji → Data Fasilitas.
Apa yang ditampilkan¶
List page — ringkasan tiap Fasilitas Progressif aktif untuk periode lewat terakhir:
| Kolom | Sumber |
|---|---|
| Nama Fasilitas | fasilitas_progressif.nama |
| Satuan | HARIAN / BULANAN |
| Total Karyawan | COUNT karyawan dengan jabatan.level ada di tarif |
| Perkiraan Nominal | Σ(tarif_per_golongan × jumlah_karyawan × pengali_hari) |
| Aksi | tombol Detail |
Pengali hari:
- BULANAN: × 1
- HARIAN: × HARI_KERJA_DEFAULT (= 22 saat ini, hardcoded)
Detail per fasilitas¶
Klik Detail → halaman detail dengan:
- Header: periode + kode/nama fasilitas + satuan badge + total karyawan + hari kerja default
- Search: NIK / nama
- Tabel: NIK | Nama Karyawan | Golongan | Nominal
- Untuk satuan HARIAN, tiap nominal punya keterangan (× 22 hari)
Tujuan¶
Preview budget tunjangan fasilitas progressif sebelum kalkulasi. Verifikasi: - Karyawan yang ada di list = yang punya golongan jabatan match tarif - Nominal per karyawan = tarif × pengali hari
Catatan: Halaman ini view saja — TIDAK trigger kalkulasi. Real kalkulasi dilakukan di sub-menu Kalkulasi.
7.3 Data Pinjaman¶
Akses: Proses Gaji → Data Pinjaman.
Apa itu¶
CRUD transaksi pinjaman karyawan. Setiap pinjaman = 1 angsuran flat per bulan dari range periode tertentu.
Tampilan¶
Tabel:
| Kolom | Sumber |
|---|---|
| NIK | karyawan.nomor_induk |
| Karyawan | biodata.nama |
| Jenis Pinjaman | kode + nama dari master Jenis Pinjaman |
| Periode | mulai — akhir (mis. "Februari 2026 — Desember 2026") + jumlah_bulan |
| Nominal/Bulan | nominal_bulanan |
| Total Pinjaman | computed = nominal × jumlah_bulan |
| Aksi | Edit / Hapus |
Toolbar: search by nama jenis / NIK / nama karyawan + tombol Tambah Pinjaman.
Tambah Pinjaman¶
- Klik "Tambah Pinjaman"
- Modal editor:
- Jenis Pinjaman: dropdown dari master aktif
- Karyawan: picker dependent on jenis
- Filter: hanya karyawan yang belum punya pinjaman jenis ini
- Search NIK / nama
- Auto-clear saat user ganti jenis pinjaman
- Periode Mulai + Periode Akhir:
- 2 dropdown per periode (Bulan: Januari-Desember + Tahun: -3..+5 dari sekarang)
- Nominal Bulanan (Rp): angka > 0
- Total Pinjaman: read-only auto-compute, tampil sebagai chip warna terracotta dengan keterangan "X bulan × Rp Y"
- Deskripsi: opsional
- Simpan
Validasi¶
- Jenis & karyawan harus exist di tenant
periode_akhir ≥ periode_mulainominal > 0- Bulan 1-12
Edit¶
- Klik Edit di row → modal sama
- Field bisa diubah (kecuali komponen + karyawan untuk Pengecualian, tapi Data Pinjaman tidak ada constraint ini)
Hapus¶
Soft delete. Data tetap di DB untuk audit.
Konsumsi di Kalkulasi¶
Saat kalkulasi gaji, untuk komponen model=PINJAMAN:
- Sistem cari pinjaman karyawan dengan jenis = komponen.value_ref_id
- Cek apakah periode aktif overlap dengan periode_mulai..periode_akhir
- Bila ya → ambil nominal_bulanan sebagai nominal komponen
7.4 Data Upload¶
Akses: Proses Gaji → Data Upload.
Apa itu¶
Untuk komponen gaji model=UPLOAD: nominal per karyawan diisi manual via CSV. Cocok untuk:
- Bonus performance (variable per orang per periode)
- Insentif khusus
- Penyesuaian gaji bulanan
List Page¶
Tabel komponen model=UPLOAD aktif:
| Kolom | Sumber |
|---|---|
| Kode + Nama Komponen | komponen_gaji |
| Kategori | badge berwarna |
| Karyawan Terupload | COUNT row di komponen_gaji_upload periode tsb |
| Total Nominal | SUM nominal |
| Aksi | Detail |
Detail per komponen¶
Klik Detail → halaman dengan:
Header card:
- Periode + kode/nama komponen + badge kategori
- Stat: total karyawan terupload + total nominal
- 3 tombol kanan atas:
- Template CSV — download template dengan header nik,nominal + 1 contoh row
- Replicate Previous — copy data dari periode sebelum (auto-disabled bila tidak ada periode sebelum)
- Upload CSV — modal upload
Search bar: NIK / nama karyawan, debounced.
Tabel karyawan:
- NIK | Nama | Nominal | Aksi
- Aksi: Edit (update nominal single karyawan) / Hapus (single row)
Upload CSV¶
- Klik "Upload CSV"
- Modal upload:
- Pilih file CSV (header
nik,nominal) - Klik "Upload & Replace"
- Validasi all-or-nothing:
- Header harus
nik,nominal(case-insensitive) - NIK exists di tenant + active
- Nominal numeric ≥ 0 (tolerant locale:
1.000.000,50dan1000000.50keduanya valid) - Tidak ada duplikat NIK di CSV
- Bila ada error:
- List error tampil di modal:
Baris | NIK | Masalah - Tidak ada data yang berubah (rollback)
- User perbaiki CSV → upload ulang
- Bila valid:
- DELETE seluruh row periode tsb
- INSERT semua row dari CSV
- Toast feedback: "N row dimasukkan, data periode di-replace"
Replicate from Previous Periode¶
- Klik "Replicate Previous"
- Modal konfirmasi: "Salin data upload dari [periode sebelumnya]?" + warning replace
- Klik Replicate
- Sistem:
- Cari periode sebelum periode lewat terakhir (yang
tanggal_selesai < periode_lewat.tanggal_mulaiterbaru) - Bila ada data → DELETE periode aktif + INSERT copy
- Toast: "X row di-replicate dari [kode periode sumber]"
Tip: Pakai Replicate untuk kasus stable (mis. tunjangan posisi yang sama tiap bulan), lalu edit beberapa row spesifik.
Edit single row¶
Klik Edit di row tabel → modal kecil dengan input nominal. Update tanpa upload ulang seluruh CSV.
Delete single row¶
Klik Hapus → konfirmasi → row terhapus dari periode tsb.
7.5 Pengecualian¶
Akses: Proses Gaji → Pengecualian.
Apa itu¶
Override final untuk 1 komponen × 1 karyawan × periode aktif. Saat kalkulasi gaji menemukan pengecualian: - Bypass kalkulasi standar (formula/fasilitas/upload/pinjaman) — pakai nominal pengecualian apa adanya - Nominal = 0 = eksplisit "karyawan tidak berhak komponen ini di periode ini"
Use cases¶
- Karyawan probation tidak dapat tunjangan tertentu untuk periode tsb
- Bonus khusus 1 karyawan yang tidak follow formula
- Koreksi gaji bulan berjalan (set 0 untuk komponen yang seharusnya tidak masuk)
Tampilan¶
Tabel:
| Kolom | Penjelasan |
|---|---|
| Komponen | Badge kategori + nama + kode |
| NIK + Nama Karyawan | Karyawan yang di-override |
| Nominal | Nilai final (atau "Tidak Berhak" merah bila 0) |
| Deskripsi | Catatan alasan |
| Aksi | Edit / Hapus |
Toolbar: search (kode/nama komponen / NIK / nama karyawan), tombol Replicate Previous, tombol Tambah Pengecualian.
Tambah Pengecualian¶
- Klik "Tambah Pengecualian"
- Modal:
- Komponen Gaji: dropdown dari semua komponen aktif (with badge kategori)
- Karyawan: picker dependent on komponen
- Filter: hanya karyawan yang belum punya pengecualian untuk komponen + periode aktif
- Auto-reset saat ganti komponen
- Nominal Final (Rp): angka ≥ 0
- Help text: "Isi 0 jika karyawan tidak berhak komponen ini di periode aktif"
- Deskripsi: opsional (alasan pengecualian)
- Simpan
Validasi¶
- UNIQUE composite (komponen, periode, karyawan) di DB → duplikat ditolak
409 DUPLICATE_PENGECUALIAN - Nominal ≥ 0
Edit¶
Modal sama, tapi komponen + karyawan disabled — hanya nominal & deskripsi yang bisa diubah (integrity composite key terjaga).
Hapus¶
Hard delete. Setelah dihapus, kalkulasi standar dipakai lagi untuk karyawan ini.
Replicate Previous¶
Sama pattern dengan Data Upload: copy seluruh pengecualian dari periode sebelum, replace yang aktif.
7.6 Kalkulasi¶
Akses: Proses Gaji → Kalkulasi.
Apa itu¶
Eksekusi kalkulasi gaji untuk seluruh komponen × karyawan periode lewat terakhir. Idempotent — re-run = full replace seluruh row periode tsb.
Tampilan halaman¶
Header: tombol Kalkulasi kanan atas + deskripsi.
Periode info bar: - Kode periode + range tanggal - Last run timestamp (mis. "Terakhir di-kalkulasi: 02/05/2026 14:30")
3 Kategori summary cards: - Pendapatan (border hijau) — total komponen + total nominal - Potongan (border merah) — total komponen + total nominal - Benefit (border indigo) — total komponen + total nominal
Tabel per komponen:
| Kolom | Penjelasan |
|---|---|
| Kode | komponen kode |
| Nama (+ Model) | komponen nama + Model di subtitle |
| Kategori | badge berwarna |
| Total Karyawan | COUNT karyawan menerima nominal > 0 |
| Total Nominal | SUM nominal |
| Aksi | tombol Lihat (modal detail karyawan) |
Klik tombol "Kalkulasi"¶
- Klik tombol Kalkulasi di kanan atas
- Modal konfirmasi terbuka — pilih mode:
Mode 1: Semua karyawan (full replace) — default - Warning: "Idempotent — hasil kalkulasi sebelumnya untuk periode tsb akan diganti seluruhnya." - Cocok untuk kalkulasi awal periode atau saat ada perubahan global (upload data baru, master komponen diubah, dst)
Mode 2: Sebagian karyawan (partial) — recompute subset - Modal melebar; muncul karyawan picker dengan search NIK/nama (debounce 250ms) - Klik checkbox per karyawan; counter "N karyawan terpilih" tampil di bawah - Tombol "Hapus semua (N)" untuk reset pilihan - Tombol "Jalankan (N karyawan)" disabled sampai minimal 1 karyawan dipilih - Hanya karyawan terpilih yang di-recalc; row karyawan lain di periode TIDAK disentuh - Use case: HR update Setting Karyawan / pinjaman / upload untuk subset karyawan dan ingin recompute cepat tanpa menjalankan kalkulasi penuh
- Klik Jalankan (atau Jalankan (N karyawan) untuk partial)
- Sistem run idempotent. Pre-load 7 batch query → loop pure compute (zero DB hit per iterasi). Durasi: 18 karyawan × 3 komponen ≈ 60 ms (full mode); ~50 ms (partial 3 karyawan). Untuk tenant ribuan karyawan, performa scales jauh lebih baik dari implementasi sebelumnya.
- Toast feedback: "Kalkulasi full selesai" atau "Kalkulasi partial selesai" —
N row tersimpan untuk K karyawan × C komponen (Xms) - Tabel auto-refresh; modal otomatis tertutup; mode reset ke "Semua karyawan" untuk run berikutnya
Algoritma resolusi (per komponen × karyawan)¶
Untuk setiap kombinasi (komponen aktif × karyawan aktif tenant):
- Cek Pengecualian DULU (early return):
- Ada record
pengecualian_komponen_gaji(komponen, karyawan, periode)? → pakai nominal-nya, bypass semua step lain nominal=0eksplisit → tidak insert row- Status Filter (
komponen_gaji_statusM:N): - Komponen tanpa filter status sama sekali = no one eligible (skip)
- Karyawan dengan status yang ADA di list → lewat
- Karyawan dengan status NOT in list → skip
- Karyawan dengan status NULL (Setting Karyawan belum di-isi) → permisif, lewat sebagai "default eligible"
- Resolusi by
komponen.model: - FORMULA: evaluate
formula.expressiondenganFormulaContext{karyawan_id, periode_id}— error → 0 - FASILITAS: tarif by
jabatan.level× pengali_hari (HARIAN: 22, BULANAN: 1) — tidak ada tarif → 0 - UPLOAD: lookup
komponen_gaji_upload(komponen, periode, karyawan) — tidak ada → 0 - PINJAMAN: cari
pinjaman_karyawanoverlap periode aktif — tidak ada → 0 - Filter nominal: hanya nominal > 0 yang INSERT — total karyawan = COUNT rows.
Idempotency¶
- Full mode: DELETE seluruh row periode → re-INSERT untuk semua karyawan tenant. Hasil run sama bila input data tidak berubah.
- Partial mode: DELETE row hanya untuk karyawan terpilih → re-INSERT untuk karyawan tsb. Row karyawan lain di periode TIDAK disentuh. Total DB rows tetap konsisten.
- Edge case mixed valid/invalid
karyawan_ids→ hanya yang valid diproses; semua invalid → run gagal dengan message error.
Detail per Komponen (modal "Lihat")¶
Klik Lihat di row → modal popup:
Header info: - Badge kategori + nama komponen + Model - Total: N karyawan + Rp total
Tabel karyawan:
- NIK | Nama Karyawan | Nominal
- Untuk POTONGAN, nominal tampil warna merah
7.7 Konfirmasi (Tutup Bulan)¶
Akses: Proses Gaji → Konfirmasi.
Halaman terakhir di alur Proses Gaji. Fungsinya: review hasil kalkulasi terakhir vs periode sebelumnya lalu Finalize (= Tutup Bulan) untuk arsip periode.
Tampilan¶
2 tab review side-by-side komparasi periode aktif vs periode arsip terakhir:
Tab "Komponen Gaji" (Gap IDR)
- Tiap baris = 1 komponen gaji aktif
- Kolom: Komponen | Kategori | Nominal Periode Lewat | Nominal Periode Aktif | Selisih (Rp)
- Selisih = Nominal_Lewat − Nominal_Aktif. Positif → komponen menurun (perlu cek), negatif → naik.
- Klik baris → modal breakdown per karyawan untuk komponen tsb (lihat siapa yang berkontribusi pada selisih).
Tab "Karyawan" (Gap %)
- Tiap baris = 1 karyawan
- Kolom: NIK | Nama | THP Periode Lewat | THP Periode Aktif | Gap %
- Gap % = (THP_Lewat − THP_Aktif) ÷ THP_Lewat × 100. Useful untuk identify karyawan dengan perubahan take-home extreme (mis. gap > 20% — kemungkinan ada error setup).
- Klik baris → modal breakdown per komponen untuk karyawan tsb.
Banner peringatan¶
Di atas tabel ada banner kalau ada anomali: - Karyawan baru (tidak ada di periode lewat) → ditandai khusus - Karyawan exit (di periode lewat tapi tidak di periode aktif) - Komponen dengan total 0 di periode aktif tapi tidak 0 di periode lewat
Input jumlah_hadir_seharusnya¶
Field di header halaman — HR input berapa hari kerja "seharusnya" di periode aktif (default = weekday count Senin-Jumat). Dipakai saat Tutup Bulan untuk hitung evaluasi_kelebihan_hari_kerja. Tidak perlu di-isi ulang kalau sudah benar.
Tombol "Konfirmasi & Tutup Bulan"¶
Setelah HR ack (centang 2 checkbox di footer): 1. ☐ Saya sudah review komponen gaji 2. ☐ Saya sudah review data karyawan
Klik tombol Konfirmasi & Tutup Bulan. Aksi yang dijalankan (transactional):
- MySQL: snapshot 4 tabel evaluasi (excess_working_hours, kelebihan_hari_kerja, selisih_absensi, akumulasi_lembur)
- MySQL: set
periode_waktu_kerja.is_archived = true+diarsipkan_pada = now() - MySQL: create / promote periode bulan berikutnya jadi AKTIF baru
- MySQL: generate
jadwal_harian_karyawanuntuk periode baru (kalau belum) - Redis: clear koreksi manual periode lama
- Elasticsearch (post-commit, best-effort): index
hris-realisasi,hris-evaluasi,hris-jadwal-harianuntuk periode yg baru di-arsip → jadi data sumber Laporan (Bab 9)
Response berisi counter ES indexing:
Kalau ES indexing fail (mis. ES down) → DB tetap committed, response sukses. Backfill manual via POST /api/v1/admin/periode/{id}/reindex-archive saat ES sudah up.
Penting¶
- Tutup Bulan tidak bisa di-undo via UI. Periode arsip = read-only untuk realisasi.
- Periode baru otomatis dibuat dengan
tanggal_mulai = bulan_berikut_pola_tenant,tanggal_cut_off = tanggal_selesai + 3 hari(default). - Hasil kalkulasi terakhir di periode arsip tetap tersimpan di
kalkulasi_komponen_karyawan(untuk audit + sumber Gaji Non Reguler periode_dasar).
7.8 Alur kerja typical¶
Untuk eksekusi payroll periode "April 2026":
| # | Aksi | Lokasi |
|---|---|---|
| 1 | Pastikan Tutup Bulan April sudah dilakukan | Pipeline → Periode |
| 2 | Review Data Waktu Kerja (hari kerja + lembur) | Proses Gaji → Data Waktu Kerja |
| 3 | Cek Data Fasilitas (preview budget tunjangan) | Proses Gaji → Data Fasilitas |
| 4 | Update Data Pinjaman (kalau ada karyawan baru pinjam atau lunas) | Proses Gaji → Data Pinjaman |
| 5 | Upload CSV / Replicate Previous untuk Data Upload (komponen variabel) | Proses Gaji → Data Upload |
| 6 | Set Pengecualian untuk karyawan khusus | Proses Gaji → Pengecualian |
| 7 | Klik Kalkulasi — verifikasi hasil | Proses Gaji → Kalkulasi |
| 8 | Bila ada anomaly: balik ke step 4-6, fix, klik Kalkulasi lagi | (idempotent) |
| 9 | Buka Konfirmasi, review tab Komponen + tab Karyawan vs periode lewat | Proses Gaji → Konfirmasi |
| 10 | Set jumlah_hadir_seharusnya (default = weekday count) |
(header halaman Konfirmasi) |
| 11 | Centang 2 ack checkbox → klik Konfirmasi & Tutup Bulan | (final, tidak bisa di-undo) |
| 12 | Verifikasi: periode arsip, periode baru AKTIF, ES ter-index (lihat response) | — |
| 13 | Data sekarang bisa di-lihat di menu Laporan (Bab 9) | Sidebar → Laporan |
7.9 Tips & Pitfalls¶
| Tips | Catatan |
|---|---|
| Setting Karyawan WAJIB lengkap | gaji_pokok + status; karyawan tanpa setting = nominal 0 di komponen FORMULA pakai [GP] |
| Pengecualian = ultimate override | Cek sebelum klik Kalkulasi — pengecualian salah bisa override unintended |
| Replicate Previous = copy + replace | Bukan merge. Akan hilangkan data periode aktif kalau ada |
| CSV Upload all-or-nothing | Lebih baik fix semua error sekaligus daripada partial-success ambiguous |
| Kalkulasi idempotent — aman re-run | Tidak ada side effect duplikasi |
| Re-run setelah Pengecualian/Upload diubah | Kalkulasi tidak otomatis trigger; manual klik Kalkulasi |
| Pitfalls | Solusi |
|---|---|
Banyak karyawan dapat nominal 0 di komponen FORMULA [GP] |
Cek Setting Karyawan: gaji_pokok belum di-isi |
| Komponen UPLOAD total karyawan = 0 | Belum upload CSV / replicate |
| Komponen PINJAMAN total = 0 | Cek Data Pinjaman: ada karyawan dengan periode overlap periode aktif? |
| Komponen FASILITAS total karyawan kurang | Karyawan tanpa jabatan.level atau tarif tidak cover semua golongan |
| Status filter terlalu strict | Cek: komponen.statuses cover semua status karyawan? |
| Karyawan baru tidak masuk kalkulasi | Pastikan mereka ada di periode lewat terakhir + sudah lengkap Setting Karyawan |
7.10 Seed data testing¶
Untuk demo / testing, tersedia script Python untuk bulk-set data master per karyawan:
Default tenant_id=1. Script idempotent — re-run aman.
Yang di-seed:
- payroll_setting untuk semua karyawan: gaji_pokok by jabatan.level (Rp 18jt G1 → Rp 4jt G10) + random status (TETAP 70% / KONTRAK 25% / MAGANG 5%) + bank + nomor rekening
- komponen_gaji_upload untuk komponen Model=UPLOAD aktif: random Rp 200rb-1.5jt per karyawan
- pinjaman_karyawan untuk komponen Model=PINJAMAN: 30% karyawan, tenor 3-12 bulan, nominal Rp 500rb-2.5jt/bulan
Setelah jalan, langsung klik Kalkulasi via UI untuk lihat hasil real.
Langkah berikutnya¶
Selamat — Anda sudah selesai full alur dari pendaftaran tenant sampai kalkulasi gaji.
→ Lihat 99. Lampiran untuk: - Konstanta Formula lengkap - Glosarium istilah HRIS - Troubleshooting umum