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:
- Header dengan breadcrumb "Laporan / Waktu Kerja" + judul + deskripsi sumber data
- Filter periode — dropdown periode arsip (urut terbaru → terlama). Saat dipilih, tabel + summary di-fetch ulang.
- Summary cards (4 kartu di atas) — metric kunci ringkas
- Tabel data per karyawan dengan paginasi 20/50/100/250
- Search NIK/nama (client-side, filter live)
- Tombol "Download Excel" di header tabel — export seluruh data laporan (bukan paginated) sebagai file
.xlsxreal (Office 2007+, openpyxl). Lihat sub-bab "Download Excel" di bawah. - (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_liburbelum 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)