Lewati ke isi

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 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

  1. Klik "Tambah Pinjaman"
  2. Modal editor:
  3. Jenis Pinjaman: dropdown dari master aktif
  4. Karyawan: picker dependent on jenis
    • Filter: hanya karyawan yang belum punya pinjaman jenis ini
    • Search NIK / nama
    • Auto-clear saat user ganti jenis pinjaman
  5. Periode Mulai + Periode Akhir:
    • 2 dropdown per periode (Bulan: Januari-Desember + Tahun: -3..+5 dari sekarang)
  6. Nominal Bulanan (Rp): angka > 0
  7. Total Pinjaman: read-only auto-compute, tampil sebagai chip warna terracotta dengan keterangan "X bulan × Rp Y"
  8. Deskripsi: opsional
  9. Simpan

Validasi

  • Jenis & karyawan harus exist di tenant
  • periode_akhir ≥ periode_mulai
  • nominal > 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

  1. Klik "Upload CSV"
  2. Modal upload:
  3. Pilih file CSV (header nik,nominal)
  4. Klik "Upload & Replace"
  5. Validasi all-or-nothing:
  6. Header harus nik,nominal (case-insensitive)
  7. NIK exists di tenant + active
  8. Nominal numeric ≥ 0 (tolerant locale: 1.000.000,50 dan 1000000.50 keduanya valid)
  9. Tidak ada duplikat NIK di CSV
  10. Bila ada error:
  11. List error tampil di modal: Baris | NIK | Masalah
  12. Tidak ada data yang berubah (rollback)
  13. User perbaiki CSV → upload ulang
  14. Bila valid:
  15. DELETE seluruh row periode tsb
  16. INSERT semua row dari CSV
  17. Toast feedback: "N row dimasukkan, data periode di-replace"

Replicate from Previous Periode

  1. Klik "Replicate Previous"
  2. Modal konfirmasi: "Salin data upload dari [periode sebelumnya]?" + warning replace
  3. Klik Replicate
  4. Sistem:
  5. Cari periode sebelum periode lewat terakhir (yang tanggal_selesai < periode_lewat.tanggal_mulai terbaru)
  6. Bila ada data → DELETE periode aktif + INSERT copy
  7. 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

  1. Klik "Tambah Pengecualian"
  2. Modal:
  3. Komponen Gaji: dropdown dari semua komponen aktif (with badge kategori)
  4. Karyawan: picker dependent on komponen
    • Filter: hanya karyawan yang belum punya pengecualian untuk komponen + periode aktif
    • Auto-reset saat ganti komponen
  5. Nominal Final (Rp): angka ≥ 0
    • Help text: "Isi 0 jika karyawan tidak berhak komponen ini di periode aktif"
  6. Deskripsi: opsional (alasan pengecualian)
  7. 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"

  1. Klik tombol Kalkulasi di kanan atas
  2. 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

  1. Klik Jalankan (atau Jalankan (N karyawan) untuk partial)
  2. 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.
  3. Toast feedback: "Kalkulasi full selesai" atau "Kalkulasi partial selesai" — N row tersimpan untuk K karyawan × C komponen (Xms)
  4. 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):

  1. Cek Pengecualian DULU (early return):
  2. Ada record pengecualian_komponen_gaji (komponen, karyawan, periode)? → pakai nominal-nya, bypass semua step lain
  3. nominal=0 eksplisit → tidak insert row
  4. Status Filter (komponen_gaji_status M:N):
  5. Komponen tanpa filter status sama sekali = no one eligible (skip)
  6. Karyawan dengan status yang ADA di list → lewat
  7. Karyawan dengan status NOT in list → skip
  8. Karyawan dengan status NULL (Setting Karyawan belum di-isi) → permisif, lewat sebagai "default eligible"
  9. Resolusi by komponen.model:
  10. FORMULA: evaluate formula.expression dengan FormulaContext{karyawan_id, periode_id} — error → 0
  11. FASILITAS: tarif by jabatan.level × pengali_hari (HARIAN: 22, BULANAN: 1) — tidak ada tarif → 0
  12. UPLOAD: lookup komponen_gaji_upload (komponen, periode, karyawan) — tidak ada → 0
  13. PINJAMAN: cari pinjaman_karyawan overlap periode aktif — tidak ada → 0
  14. 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.

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):

  1. MySQL: snapshot 4 tabel evaluasi (excess_working_hours, kelebihan_hari_kerja, selisih_absensi, akumulasi_lembur)
  2. MySQL: set periode_waktu_kerja.is_archived = true + diarsipkan_pada = now()
  3. MySQL: create / promote periode bulan berikutnya jadi AKTIF baru
  4. MySQL: generate jadwal_harian_karyawan untuk periode baru (kalau belum)
  5. Redis: clear koreksi manual periode lama
  6. Elasticsearch (post-commit, best-effort): index hris-realisasi, hris-evaluasi, hris-jadwal-harian untuk periode yg baru di-arsip → jadi data sumber Laporan (Bab 9)

Response berisi counter ES indexing:

es_archive: { realisasi_indexed: 540, evaluasi_indexed: 18, jadwal_indexed: 540 }

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:

cd backend
python -m scripts.seed_kalkulasi_data [tenant_id]

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