Lewati ke isi

9. Laporan (Reporting)

Bab ini menjelaskan menu Laporan di sidebar — kumpulan halaman reporting yang menarik data dari Elasticsearch (bukan MySQL langsung). Data ES dipopulasi otomatis saat HR melakukan Tutup Bulan di Konfirmasi Gaji (lihat Bab 7) atau via endpoint manual POST /api/v1/admin/periode/{id}/reindex-archive.

Status implementasi

Sub-laporan Status Sumber data ES
Waktu Kerja — Jadwal Kerja ✅ implemented + drill-down harian hris-jadwal-harian (per karyawan × tanggal — shift + kelompok kerja + override)
Waktu Kerja — Realisasi Kehadiran ✅ implemented + drill-down harian (clock-in/out) hris-realisasi (per karyawan × tanggal + clock_in_at/clock_out_at)
Waktu Kerja — Realisasi Lembur ✅ implemented hris-evaluasi.lembur_total_menit
Waktu Kerja — Kelebihan Waktu Kerja ✅ implemented hris-evaluasi.excess_total_menit
Penggajian — Riwayat Transfer ✅ implemented (filter bank + distribusi bar chart) hris-payroll-summary (1 dok / karyawan × periode — bank + THP)
Penggajian — Distribusi Komponen ✅ implemented + drill-down karyawan hris-payroll-komponen (1 dok / komponen × karyawan × periode)
Penggajian — Jurnal Penggajian ⏳ deferred (butuh master GL account mapping) (akan): hris-payroll-komponen + chart of accounts
Gaji Non Reguler — Riwayat Transfer ✅ implemented (per SK) MySQL (konfirmasi service, bukan ES)
Gaji Non Reguler — Komponen Income ✅ implemented + drill-down karyawan MySQL (konfirmasi service)
Gaji Non Reguler — Jurnal Penggajian ⏳ deferred (butuh master GL account mapping) (akan): konfirmasi + chart of accounts

Status: 8/10 implemented — 4 Waktu Kerja + 2 Penggajian reguler + 2 Gaji Non Reguler live; Jurnal (reguler + non reguler) menyusul setelah master GL.

Akses

Sidebar Laporan → klik salah satu dari 3 sub-group untuk expand → klik item laporan.

3 sub-group: - Waktu Kerja (4 laporan) — kehadiran, lembur, kelebihan jam kerja - Penggajian (3 laporan) — transfer, distribusi, jurnal akuntansi (siklus reguler) - Gaji Non Reguler (3 laporan) — transfer, komponen, jurnal (siklus THR/Bonus/dll)

Sub-group bisa expand/collapse independen; state terbuka tersimpan di browser (localStorage).

Prerequisite

Sebelum bisa lihat laporan apapun: 1. Minimum 1 periode harus sudah di-Tutup Bulan (Bab 6 / Bab 7 sub-bab Konfirmasi) 2. Bila tutup bulan terjadi saat ES down, jalankan POST /api/v1/admin/periode/{id}/reindex-archive untuk backfill 3. Cek health ES: GET /health/ready — pastikan komponen elasticsearch status ok

Konvensi UI umum (4 laporan Waktu Kerja)

Tiap halaman mengikuti pola yang sama:

  1. Header dengan breadcrumb "Laporan / Waktu Kerja" + judul + deskripsi sumber data
  2. Filter periode — dropdown periode arsip (urut terbaru → terlama). Saat dipilih, tabel + summary di-fetch ulang.
  3. Summary cards (4 kartu di atas) — metric kunci ringkas
  4. Tabel data per karyawan dengan paginasi 20/50/100/250
  5. Search NIK/nama (client-side, filter live)
  6. Tombol "Download Excel" di header tabel — export seluruh data laporan (bukan paginated) sebagai file .xlsx real (Office 2007+, openpyxl). Lihat sub-bab "Download Excel" di bawah.
  7. (untuk Jadwal + Realisasi Kehadiran) Tombol "Detail Harian →" per row untuk drill-down

Download Excel

Tiap laporan summary (6 laporan: 4 Waktu Kerja + 2 Penggajian) punya tombol Download Excel di pojok kanan baris filter. Klik → file .xlsx ter-download dengan format:

  • Row 1: judul laporan (mis. "Laporan Realisasi Kehadiran")
  • Row 2..N: meta — Periode, Tenant, Dicetak (timestamp)
  • Row kosong pemisah
  • Row header (indigo fill + cream font, freeze panes)
  • Row data sesuai filter aktif (search + bank/kategori jika ada)
  • Row TOTAL (bold) untuk numeric columns

Format type per kolom dipertahankan: rupiah (#,##0), persen (0.0%), tanggal/datetime, integer, decimal. Auto-width per kolom. File aman dibuka di Excel / LibreOffice / Google Sheets. Filter yang aktif di UI (search, dropdown bank, dropdown kategori) ikut diterapkan ke export.


9.1 Realisasi Kehadiran

Akses: Laporan → Waktu Kerja → Realisasi Kehadiran.

Tujuan

Audit kehadiran karyawan untuk satu periode arsip: berapa hari hadir (OS), tidak hadir (XTY), izin (IZ/AZ), cuti (CT), libur (LB), perjalanan dinas (SPD).

Summary cards (4 metric tenant-wide)

Card Definisi
% Kehadiran (hari status OS + HD) ÷ total_hari_tercatat × 100
Total Karyawan jumlah karyawan dengan data presensi periode ini
Tidak Hadir (XTY) total hari tanpa keterangan / capture tidak valid
Izin + Cuti + SPD breakdown 3 status berbayar (kalau dianggap)

Tabel per karyawan

Kolom: NIK | Nama | Total | HD | OS | XTY | IZ/AZ | CT | LB | SPD | % Hadir | Detail

Tiap angka per status = jumlah hari karyawan ada di status itu. Total = penjumlahan semua status. Hadir = OS + HD. Kalau total ≠ 30 di periode bulanan, kemungkinan karyawan baru / pensiun di tengah periode.

Drill-down "Detail Harian →"

Klik tombol pada baris karyawan → modal terbuka dengan 30 baris per tanggal:

Kolom Isi
Tanggal hari (mis. "Rab, 15 Apr 2026")
Shift kode shift terjadwal (atau "(libur)" kalau no shift)
Status badge berwarna + label (mis. "OS — On Schedule", "XTY — Tanpa Keterangan")
Hadir ✓ Hadir / — (kalau tidak hadir)
Catatan clock-in/out time untuk status hadir + indikator capture invalid; field catatan MySQL kalau ada

Khusus kolom Catatan: - Format: Masuk 07:57 · Pulang 17:37 (hijau moss) - Bila capture pulang hilang: Masuk 08:08 · Pulang — - Bila ada capture di luar window: (1 capture diabaikan) suffix terracotta — pakai ini untuk identifikasi pola "tap masuk tapi tidak tap pulang"

Row tidak hadir (selain libur) → background terracotta tipis untuk highlight visual.

Contoh use case

  • Audit absen masal: filter periode, sort by XTY descending, identifikasi siapa yang absen paling banyak. Drill-down untuk lihat hari mana saja.
  • Verifikasi pengaduan karyawan "saya tap tapi tidak tercatat": cek drill-down, kalau "(1 capture diabaikan)" muncul artinya karyawan tap di luar window jadwal — bukan tidak tap.
  • Compliance: % kehadiran < target HR → fokus ke karyawan dengan kontribusi XTY tertinggi.

9.2 Realisasi Lembur

Akses: Laporan → Waktu Kerja → Realisasi Lembur.

Tujuan

Audit total lembur DISETUJUI per karyawan untuk satu periode. Dipakai untuk verifikasi sebelum kalkulasi tunjangan lembur, atau monitor pola lembur excessive.

Summary cards

Card Definisi
Total Jam Lembur sum durasi lembur seluruh karyawan (jam + menit + jumlah pengajuan)
Karyawan Punya Lembur jumlah karyawan dengan total > 0 / total karyawan terdata + persentase
Rata-rata per Karyawan total_menit ÷ karyawan_punya_lembur (HANYA yang punya lembur, supaya tidak distorted)
Total Pengajuan jumlah pengajuan lembur disetujui di periode ini

Tabel per karyawan

Kolom: NIK | Nama | Total Jam | Total Menit | Pengajuan

Default sort: total terbanyak di atas (insight HR paling berguna). Karyawan tanpa lembur (total_menit = 0) tetap tampil tapi di-dim opacity 50% — supaya audit cakupan tetap lengkap.

Contoh use case

  • Bulan dengan target overtime ketat: cek apakah ada karyawan melebihi batas (mis. > 40 jam/bulan)
  • Validasi sebelum payroll: pastikan total lembur DISETUJUI di laporan ini match dengan tunjangan lembur yang akan dihitung di Proses Gaji
  • Pattern detection: kalau karyawan tertentu konsisten paling tinggi tiap bulan, mungkin workload tim-nya tidak balanced

9.3 Kelebihan Waktu Kerja (Excess Working Hours)

Akses: Laporan → Waktu Kerja → Kelebihan Waktu Kerja.

Tujuan

Mengukur selisih waktu masuk/keluar aktual vs jadwal — signed metric: - Positif = karyawan datang lebih awal / pulang lebih lambat dari jadwal (mungkin lembur informal) - Negatif = telat / pulang lebih awal (potensi indisipliner ringan)

Note: berbeda dengan Realisasi Lembur. Lembur = pengajuan formal yang sudah DISETUJUI. Excess = tracking informal dari clock-in/out aktual.

Summary cards

Card Definisi
Total Excess (Signed) sum total_menit_excess semua karyawan (bisa negatif!)
Karyawan Excess Positif karyawan dengan total > 0 (kerja lebih)
Karyawan Excess Negatif karyawan dengan total < 0 (telat / pulang awal)
Karyawan Excess Nol karyawan tepat sesuai jadwal

Tabel per karyawan

Kolom: NIK | Nama | Hari Tercatat | Total Excess | Rata-rata / Hari

Sort interactive — 3 tombol di atas tabel: - Excess Positif Atas (default) — paling banyak kerja lebih di atas - Excess Negatif Atas — paling banyak telat di atas - Magnitudo Terbesar — abs(value) descending, untuk lihat siapa paling extreme (positif atau negatif)

Karyawan tanpa data clock-in/out valid (hari_tercatat = 0) tidak ditampilkan — angka excess-nya tidak meaningful.

Contoh use case

  • Reward overtime informal: top 5 excess positif → kandidat apresiasi
  • Coaching disiplin: bottom 5 (paling negatif) → 1-on-1 untuk klarifikasi
  • Validasi jadwal shift: kalau semua karyawan positif besar di shift tertentu, mungkin jadwalnya kurang akurat (jam_datang/jam_pulang perlu di-review)

9.4 Jadwal Kerja

Akses: Laporan → Waktu Kerja → Jadwal Kerja.

Tujuan

Audit struktur jadwal periode arsip: berapa hari kerja vs libur, distribusi shift, jumlah override manual HR.

Summary cards

Card Definisi
Total Hari Jadwal total karyawan × jumlah hari periode
Hari Kerja row dengan shift terjadwal (waktu_kerja_id NOT NULL)
Hari Libur row tanpa shift; sub: berapa di antaranya jatuh di libur kalender
Override HR row yang last_overridden_by NOT NULL (HR ubah shift manual via Pipeline → Jadwal Karyawan)

Distribusi Shift (bar chart)

Panel di tengah halaman menampilkan horizontal bar chart: tiap shift = 1 bar, panjang proporsional dengan total hari. Diurutkan terbanyak ke terkecil. (libur) di-warna ochre, shift real di-warna moss.

Berguna untuk: - Lihat shift mana yang paling sering dipakai - Identify jadwal "longgar" (banyak hari libur) vs "padat"

Tabel per karyawan

Kolom: NIK | Nama | Kelompok Kerja | Total | Hari Kerja | Libur | Override | Shift Utama | Detail

  • Shift Utama = mode (shift yang paling sering di periode itu untuk karyawan tsb). Useful untuk identify karyawan yang konsisten 1 shift vs yang sering rotasi.
  • Override > 0 = HR pernah ubah shift manual untuk karyawan ini di periode tsb (warna terracotta untuk highlight).

Drill-down "Detail Harian →"

Klik → modal terbuka dengan 30 baris per tanggal:

Kolom Isi
Tanggal hari
Shift kode + nama shift; atau "(libur)"
Jam "08:00—17:00"
Libur Kalender nama hari libur (kalau tanggal ada di tabel hari_libur)
Override siapa override + kapan
Catatan field catatan MySQL kalau ada

Row override → background terracotta tipis; row libur → background ochre tipis (visual scan cepat).

Contoh use case

  • Verifikasi pola jadwal: pilih karyawan, lihat 30 hari — apakah pola Senin-Jumat shift pagi konsisten?
  • Audit override: tabel utama → filter override > 0 → drill-down → lihat siapa override + kapan + alasan (catatan)
  • Compliance libur kalender: cek "Hari Libur Kalender" di summary — kalau 0 di bulan yang ada libur nasional, kemungkinan master hari_libur belum di-input untuk tahun itu

9.5 Riwayat Transfer (Penggajian)

Akses: Laporan → Penggajian → Riwayat Transfer.

Tujuan

Output untuk eksekusi transfer payroll bulanan atau audit historis: list karyawan + bank + nomor rekening + THP (Take Home Pay). Bisa di-filter per bank untuk dipakai sebagai bahan upload file transfer ke bank.

Konsep THP

THP = Pendapatan − Potongan (exclude Benefit). Benefit (mis. iuran asuransi tertanggung, natura) bersifat non-cash — tidak ditransfer ke bank. Field total_benefit tetap ditampilkan terpisah untuk visibility tapi tidak masuk THP.

Summary cards

Card Definisi
Grand Total THP sum THP seluruh karyawan periode
Total Pendapatan sum komponen kategori PENDAPATAN
Total Potongan sum komponen kategori POTONGAN
Karyawan dengan Bank rasio yang punya bank di payroll-setting / total. Kalau ada karyawan tanpa bank → warning terracotta (perlu setup)

Distribusi per Bank (bar chart)

Panel di bawah summary menampilkan bar chart per bank: panjang proporsional dengan total THP. Tiap bar: kode bank + nama + karyawan count + total Rp + %. Bar (no-bank) warna terracotta untuk highlight kalau ada karyawan belum setup bank.

Filter dropdown bank

Dropdown di header (muncul kalau ada >1 bank): "Semua bank" + per bank dengan jumlah karyawan. Saat filter aktif, tabel di-narrow ke bank tsb saja.

Tabel per karyawan

Kolom: NIK | Nama | Status | Bank | No. Rekening | Pendapatan | Potongan | THP

  • Default sort: THP terbesar di atas (insight HR)
  • Search: NIK, nama, atau nomor rekening
  • Row tanpa bank (no-bank) → background terracotta tipis untuk visual scan
  • "Nama atas rekening" tampil di bawah nama karyawan jika berbeda

Contoh use case

  • Eksekusi transfer bulanan: filter ke 1 bank (mis. BCA) → export tabel sebagai PDF / screenshot → verifikasi nominal dengan bagian Treasury → upload file ke internet banking
  • Audit "transfer ke karyawan X bulan apa saja": ulang filter per periode di dropdown periode
  • Identifikasi data tidak lengkap: cek summary "Karyawan tanpa Bank" — kalau > 0, drill down ke tabel filter bank kosong → minta karyawan tsb update bank rekening
  • Cross-check anomali: row dengan THP sangat tinggi/rendah dibanding rata-rata → cek setting komponen yang terlibat (lihat 9.6 Distribusi Komponen drill-down)

9.6 Distribusi Komponen (Penggajian)

Akses: Laporan → Penggajian → Distribusi Komponen.

Tujuan

Audit sebaran nominal per komponen gaji di periode arsip: berapa karyawan ter-dampak, total nominal, range min-max. Berguna untuk: - Akuntansi/budgeting review (total beban gaji per komponen) - Identifikasi outlier (mis. komponen "Bonus" min 0 max 50jt — wajar atau anomaly?) - Cross-check sebelum publish slip gaji

Summary cards

Card Definisi
Total Pendapatan sum nominal seluruh komponen PENDAPATAN + jumlah komponen aktif
Total Potongan sum POTONGAN
Total Benefit sum BENEFIT (non-cash)
Total Komponen Aktif jumlah komponen dengan minimal 1 karyawan ter-dampak

Filter Kategori

Dropdown di header: "Semua kategori" / "Pendapatan" / "Potongan" / "Benefit". Tabel di-filter sesuai pilihan.

Tabel per komponen

Kolom: Kategori | Kode | Nama Komponen | Model | Karyawan | Total Nominal | Rata-rata | Min — Max | Detail

  • Sort: kategori (Pendapatan → Potongan → Benefit) → kode komponen
  • Model: FORMULA / FASILITAS / UPLOAD / PINJAMAN (sumber kalkulasi nilai komponen)
  • Min — Max: range nominal antar karyawan — kalau gap besar (mis. min 0 max 5jt) artinya komponen sangat variable per karyawan

Drill-down "Lihat →"

Klik tombol pada baris komponen → modal terbuka dengan list karyawan yang dapat komponen tsb:

Kolom Isi
NIK nomor induk
Nama nama karyawan
Nominal nilai komponen untuk karyawan ini
% dari Total kontribusi karyawan tsb terhadap total komponen

Sort default: nominal terbesar di atas. Banner di top modal: badge kategori + periode + jumlah karyawan + total + rata-rata.

Contoh use case

  • Audit komponen variabel: pilih komponen Model=UPLOAD (mis. "Bonus Kinerja") → lihat distribusi — wajar atau ada karyawan dengan nominal anomali tinggi?
  • Verifikasi formula: komponen Model=FORMULA harus konsisten — kalau min = max artinya semua karyawan dapat sama (flat), kalau bervariasi pakai variable seperti [GP] atau [YOS]
  • Pre-publish check: total seluruh komponen × jumlah karyawan = match dengan THP grand total di Riwayat Transfer? Verifikasi konsistensi
  • Compliance pinjaman: komponen Model=PINJAMAN → drill-down → list karyawan yang sedang punya angsuran aktif periode ini

9.7 Gaji Non Reguler — Riwayat Transfer

Akses: Laporan → Gaji Non Reguler → Riwayat Transfer.

Tujuan

Sama dengan 9.5 (Riwayat Transfer reguler), tetapi unit kerja-nya adalah 1 SK gaji non reguler (THR/Bonus/Gaji XIII/Insentif/Lainnya), bukan periode bulanan. Output: list karyawan + bank + nomor rekening + THP untuk eksekusi transfer per SK.

Sumber data

MySQL langsung via service gaji_non_reguler.get_konfirmasi_by_karyawan (resolved nominal = override per-karyawan kalau ada, else snapshot dari periode_dasar). Berbeda dengan reguler yang query ES arsip — alasan: GNR per-SK (bukan per-periode), data sudah immutable saat setup di-close, dan jumlah karyawan jauh lebih kecil. Bank info di-fetch live dari karyawan_payroll_setting.

Filter

  • Setup dropdown (utama): list semua SK non reguler yang minimal punya 1 komponen ter-attach, urut tanggal_proses descending, dengan badge status (BARU/PROSES/SELESAI) + periode dasar.
  • Bank (opsional, muncul kalau >1 bank): filter tabel ke 1 bank saja.
  • Search: NIK / nama / nomor rekening (FE-side).

Summary cards

Identik dengan 9.5 (Grand Total THP, Total Pendapatan, Total Potongan, Karyawan dengan Bank), plus indikator jenis SK + nomor SK di card pertama.

Catatan

  • THP per karyawan = total komponen kategori PENDAPATAN dikurang total POTONGAN; BENEFIT tidak ikut THP (sama dengan reguler).
  • "Karyawan tanpa bank" sering muncul di GNR untuk karyawan baru yang belum input rekening — flag terracotta supaya HR follow-up.

9.8 Gaji Non Reguler — Komponen Income

Akses: Laporan → Gaji Non Reguler → Komponen Income.

Tujuan

Sama dengan 9.6 (Distribusi Komponen reguler), tapi per 1 SK non reguler. Output: agregat per komponen (total nominal, karyawan_count, min-max) + drill-down ke list karyawan.

Drill-down "Lihat →"

Modal terbuka dengan list karyawan + nominal resolved untuk komponen tsb di SK ini. Format dan kolom identik dengan reguler (NIK / Nama / Nominal / % dari Total). Banner top modal: badge kategori + jenis SK + nomor SK + jumlah karyawan + total + rata-rata.

Contoh use case

  • Audit THR: pilih SK THR → lihat komponen "Tunjangan Hari Raya" → cek distribusi min-max apakah wajar (mis. flat 1 bulan gaji vs gradiasi per status kekaryawanan).
  • Verifikasi override: kalau ada karyawan dengan nominal beda dari snapshot, drill-down akan tampilkan nilai final (override). Cross-cek dengan halaman Setup → Override Karyawan untuk audit perubahan manual HR.

Troubleshooting

Laporan kosong padahal periode sudah ditutup → Cek apakah ES menerima dokumen. Buka ES head client (curl http://localhost:9200/hris-realisasi/_count) — kalau count = 0, jalankan POST /admin/periode/{id}/reindex-archive untuk backfill.

Periode tertentu tidak muncul di dropdown filter → Periode itu tidak punya dokumen di ES. Indexer hanya jalan saat tutup-bulan. Untuk backfill data lama, pakai endpoint /reindex-archive (butuh periode is_archived=true).

Clock-in/out kosong padahal status OS → Anomali — OS by definition butuh capture valid in/out. Kemungkinan: (a) data presence_captures di-clear setelah tutup-bulan, atau (b) re-index periode pakai versi indexer lama (tanpa clock fields). Jalankan reindex.

Realisasi Lembur menunjukkan 0 untuk seluruh karyawan → Cek apakah ada lembur DISETUJUI di periode itu (lembur PENDING tidak masuk evaluasi). Buka Pengajuan → Lembur, filter periode + status DISETUJUI.

Detail Harian tidak muncul tombol-nya → Hanya 2 laporan (Jadwal Kerja + Realisasi Kehadiran) yang punya drill-down harian. Realisasi Lembur + Kelebihan WK pakai data agregat per-periode (hris-evaluasi), tidak ada grain harian.


Catatan teknis

Untuk admin sistem yang ingin custom query / dashboard eksternal (mis. Kibana, Grafana): - 5 index accessible di http://localhost:9200: hris-realisasi, hris-evaluasi, hris-jadwal-harian, hris-payroll-summary, hris-payroll-komponen - Field schema + grain detail: pipeline.md section "Arsip Elasticsearch" - Re-index endpoint untuk backfill: POST /api/v1/admin/periode/{id}/reindex-archive (HR-only)