6. Pipeline Periode¶
Bab ini menjelaskan lifecycle 1 periode payroll: dari membuat periode baru, generate jadwal harian, kalkulasi realisasi, sampai tutup bulan.
Konsep¶
Periode Waktu Kerja = 1 bulan payroll (mis. "April 2026"). Setiap periode punya:
- kode_periode — auto-generated dari pola tenant + (year, month), mis. 2026-04
- tanggal_mulai, tanggal_selesai — range tanggal periode
- tanggal_cut_off — deadline submit pengajuan setelah tanggal_selesai
- is_archived — kalau true, data sudah dipindah ke Elasticsearch (cold storage)
Status periode (computed at view time):
- LEWAT — tanggal_selesai < today()
- AKTIF — tanggal_mulai ≤ today() ≤ tanggal_cut_off
- AGENDA — tanggal_mulai > today()
Sistem hanya simpan 4 periode terakhir di MySQL (hot data). Sisanya di Elasticsearch.
6.1 Buat Periode Baru¶
Akses: sidebar Pipeline → Periode.
Tampilan halaman¶
Tabel periode dengan kolom: - Kode Periode - Tanggal Mulai - Selesai - Cut-off - Status (badge) - Aksi (Edit / Tutup Bulan / Archive)
Tambah Periode¶
- Klik "+ Tambah Periode"
- Modal:
- Tahun: dropdown (tahun ini ± 2)
- Bulan: dropdown 1-12
- Preview otomatis tampil:
- Pola yang dipakai (mis. "Tanggal 1 - akhir bulan, cut-off 25")
- Kode periode yang akan dibentuk:
2026-04 - Tanggal mulai:
2026-04-01, selesai:2026-04-30 - Cut-off default:
2026-05-25(overridable)
- Tanggal Cut-Off: bisa di-override bila beda dengan default tenant
- Simpan
Validasi¶
- Periode tidak boleh overlap dengan periode existing (tanggal_mulai ≤ tanggal_selesai_lain)
- Cut-off harus < tanggal_selesai periode berikutnya
Edit Periode¶
- Bisa edit
kode_periode(read-only setelah ada data) dantanggal_cut_off - Tidak bisa edit
tanggal_mulai/tanggal_selesaisetelah adajadwal_harian_karyawan(akan ada banyak side effect)
6.2 Generate Jadwal Harian¶
Setelah periode dibuat, langkah berikutnya: materialize jadwal harian per karyawan untuk seluruh hari di periode tsb.
Apa yang terjadi¶
Untuk setiap (karyawan × tanggal di periode):
1. Cek apakah tanggal tsb adalah hari_libur tenant → bila ya, set waktu_kerja_id = NULL (libur, tidak ada shift)
2. Cek assignment kelompok kerja karyawan yang aktif di tanggal tsb
3. Lookup jadwal_kerja dari assignment + day-of-week → dapat waktu_kerja_id
4. Insert row jadwal_harian_karyawan(periode_id, karyawan_id, tanggal, waktu_kerja_id)
Trigger Generate¶
Akses: Pipeline → Periode atau Pipeline → Jadwal Karyawan.
- Pilih periode (mis. periode AGENDA yang baru dibuat)
- Klik tombol "Generate Jadwal Harian"
- Konfirmasi modal — proses dijalankan via Redis job (async untuk periode besar)
- Sistem return summary:
karyawan_counthari_countrows_insertedhari_libur_count(tanggal yang di-skip karena libur)rows_with_shiftvsrows_libur
View Jadwal Karyawan¶
Setelah generate, akses Pipeline → Jadwal Karyawan: - Tabel daftar karyawan + ringkasan jadwal periode aktif - Klik karyawan → halaman detail jadwal harian (per tanggal, shift apa) - Search NIK/nama karyawan di toolbar atas tabel (debounce 300ms, auto-apply). Total ikut filter — counter "X karyawan cocok ..." muncul di subtitle Panel.
Override Manual¶
Di halaman Jadwal Karyawan Detail: - Klik "Edit" per row tanggal → ganti shift untuk karyawan tertentu di tanggal tertentu - Mis. karyawan A pindah shift sehari karena event khusus
Catatan: override manual ini independent dari Tukar Shift workflow. Tukar shift = swap antar 2 karyawan; override = HR force change.
Pengecualian Jadwal Harian (upload Excel bulk)¶
Selain Override Manual per cell, HR bisa upload Excel bulk untuk override jadwal banyak karyawan-tanggal sekaligus. Cocok untuk skenario: - Jadwal hasil generate tidak sesuai realita lapangan (mis. roster shift dokter yang di-set di luar app) - HR sudah punya Excel jadwal dari sumber lain (sistem lama) dan ingin import
Akses: Halaman Pipeline → Jadwal Karyawan → klik "Pengecualian Jadwal Harian" (di sebelah tombol Generate).
Alur:
- Klik "Download template Excel" di modal — sistem generate file
.xlsxdengan: - Row 1 (header):
NIK | <tanggal periode>— kolom diurutkan sesuai pola tenant. Untuk pola cross-month (mis. 11-10), kolom =11, 12, …, 30, 01, 02, …, 10. - Row 2 (label bulan):
Apr/Meidst. sebagai bantuan visual — tidak dibaca server. - Row 3+: pre-fill semua NIK karyawan aktif, kolom tanggal kosong.
- Buka file di Excel, isi cell tanggal yang mau di-override:
- Kode shift (sesuai master Waktu Kerja, mis.
N,NJ,1) → set ke shift tsb - Token libur:
X,OFF,L,LIBUR, atau-→ paksa libur (waktu_kerja kosong) - Kosong → jangan override cell ini (biarkan hasil generate apa adanya)
- Save, kembali ke modal → klik "Pilih file Excel…" → klik Upload.
- Sistem return ringkasan:
cells_overridden— jumlah cell yang berhasil di-applyrows_processed— jumlah karyawan yang diprosescells_skipped_out_of_range— cell yang di-skip karena tanggalnya di luar periode aktiferrors— list error per row (NIK tidak ditemukan, kode shift invalid, dll. — max 50 baris)
Penting — interaksi dengan tombol Generate:
Row hasil upload Pengecualian punya flag last_overridden_at di DB. Saat Anda klik Generate Jadwal Harian berikutnya:
- Row override dipertahankan (TIDAK ditimpa)
- Row non-override di-regen ulang dari pola
- Response generate include rows_preserved_override dan rows_skipped_override
Artinya HR aman re-run Generate kapan saja tanpa kehilangan pengecualian yang sudah di-upload.
Re-upload semantics:
Upload Excel berikutnya MEREPLACE data per-cell yang berisi: - Cell berisi → override row (apapun sumbernya: generate atau upload sebelumnya) di-replace - Cell kosong → row existing TIDAK disentuh (biarkan apa adanya)
Untuk "menghapus" override dan kembali ke hasil generate, edit row langsung di halaman Detail (set last_overridden_at = NULL via fitur Reset Override di kalender, atau langsung ubah ke shift yang dimaksud).
6.3 Kalkulasi Realisasi¶
Akses: Pipeline → Kalkulasi Kerja.
Apa itu¶
Realisasi = hasil kalkulasi presensi harian per karyawan: hadir / izin / cuti / dinas / libur, dengan jam datang/pulang aktual.
Sumber data:
- jadwal_harian_karyawan (target shift)
- presence_capture (clock-in/out raw dari mesin biometrik)
- cuti, ijin, lembur, pertukaran_shift, perjalanan_dinas (semua DISETUJUI)
- hari_libur
Trigger¶
- Pilih Periode (default: aktif)
- Klik "Kalkulasi Realisasi" (atau "Kalkulasi Tunggal" lalu pilih 1–10 karyawan)
- Overlay progress muncul — halaman freeze, tidak bisa di-klik tutup. Progress bar gradient indigo→ochre dengan persentase besar (mis. 47%). Naik halus dari 0 → ~90% sambil menunggu, lalu jump ke 100% (warna hijau moss) saat selesai → overlay tutup → modal hasil muncul.
- Eksekusi sync (idempotent) — re-kalkulasi akan delete+insert ulang row realisasi periode (untuk seluruh karyawan atau subset terpilih saja).
Catatan progress: angka persentase di simulasi karena backend kalkulasi tidak menyalurkan progress real-time. Itu indikator "sedang bekerja"; durasi sebenarnya 9–30 detik tergantung skala data. Jangan tutup tab — kalau ditutup, kalkulasi tetap selesai di server tapi UI tidak dapat hasil.
Hasil¶
Per karyawan × tanggal, tersimpan di tabel realisasi:
- status_efektif: HADIR / IZ (ijin) / AZ (alpa zakat — alpha) / CT (cuti) / DN (dinas) / LB (libur)
- jam_datang_aktual, jam_pulang_aktual (kalau hadir)
- selisih_menit_datang, selisih_menit_pulang (vs target shift)
- durasi_lembur_menit
View Realisasi¶
Akses: Pipeline → Realisasi Kerja. - Tabel daftar karyawan + ringkasan periode (jumlah hari per status) - Klik karyawan → halaman detail tanggal-by-tanggal - Search NIK/nama karyawan di toolbar atas tabel (debounce 300ms, auto-apply). Total ikut filter.
Koreksi Manual + Visual di Kalender¶
Di halaman Realisasi Detail per karyawan ada 2 tab:
- Kalender — grid 7 hari × N minggu dengan badge status per hari (OS/XTY/LB/CT/IZ/AZ/SPD)
- Koreksi Manual — tabel khusus untuk tandai hari XTY (tidak hadir) sebagai koreksi:
- Centang Koreksi In (kalau clock-in hilang) atau Koreksi Out (kalau clock-out hilang)
- Isi Keterangan (alasan koreksi, mis. "Lupa absen pulang, sudah konfirmasi atasan")
- Klik Simpan Koreksi → disimpan ke Redis dengan TTL
Visual koreksi langsung tervisualisasi di tab Kalender (tanpa perlu re-kalkulasi):
- Jam yang dikoreksi muncul dengan underline indigo bold. Kalau capture aktual masih kosong, ditampilkan preview jam normatif shift (mis. shift Normal 07:30 / 16:00) — angka yang akan diterapkan saat HR menjalankan re-kalkulasi
- Dot bulat indigo di pojok kanan-atas cell sebagai marker "ada koreksi"
- Hover cell → tooltip "Clock-in dikoreksi manual → akan jadi 07:30 · Koreksi: Lupa absen…"
- Legenda kalender tambah baris: "
07:30(underlined) — jam dikoreksi manual"
Saat HR klik Kalkulasi Realisasi ulang, koreksi tersimpan di-merge:
- XTY dengan koreksi_in/out → status berubah jadi OS, jam normatif shift dijadikan clock-in/clock-out
- Status hadir di-set TRUE
- Catatan realisasi diawali [MANUAL] + keterangan koreksi
6.4 Tutup Bulan¶
Akhir periode: lakukan Tutup Bulan untuk: 1. Snapshot evaluasi (excess working hours, kelebihan hari kerja, akumulasi lembur) 2. Lock periode dari edit 3. Bikin periode berikutnya otomatis
Trigger¶
Akses: Pipeline → Periode → klik tombol "Tutup Bulan" pada periode AKTIF.
Modal konfirmasi:
- Jumlah Hari Hadir Seharusnya: input wajib (1 angka global untuk semua karyawan periode tsb, mis. 22 hari)
- Dipakai untuk hitung selisih = jumlah_hadir_seharusnya - jumlah_hadir_aktual
Klik Konfirmasi Tutup Bulan.
Apa yang terjadi (background)¶
- Insert ke
evaluasi_kelebihan_hari_kerjaper karyawan:(seharusnya, aktual, selisih) - Insert ke
evaluasi_excess_working_hoursper karyawan: total menit excess (signed) - Insert ke
evaluasi_selisih_absensiper kelompok_kerja: agregat - Insert ke
evaluasi_akumulasi_lemburper karyawan: total durasi lembur DISETUJUI di periode tsb - Bikin periode baru (pola tenant: bulan berikutnya)
- Generate jadwal harian periode baru (auto-trigger)
Hasil di UI¶
- Status periode lama:
AKTIF→LEWAT - Periode baru muncul di list dengan status
AGENDAatauAKTIF(tergantung tanggal hari ini) - Menu Pipeline → Evaluasi sekarang tampilkan data periode tsb
Setelah Tutup Bulan¶
Data evaluasi terkunci. Realisasi tetap bisa di-edit (untuk koreksi historis) tapi evaluasi tidak auto-recalculate (manual re-trigger Tutup Bulan kalau perlu).
6.5 Evaluasi (View)¶
Akses: Pipeline → Evaluasi.
4 Tabel snapshot¶
Halaman Evaluasi tampilkan 4 snapshot:
- Excess Working Hours per karyawan: total menit excess (positif = lebih, negatif = kurang)
- Kelebihan Hari Kerja per karyawan: hadir seharusnya, aktual, selisih
- Selisih Absensi per kelompok kerja: agregat menit excess
- Akumulasi Lembur per karyawan: total durasi menit DISETUJUI
Filter¶
- Periode (dropdown)
- Karyawan (search)
- Kelompok Kerja (untuk tab 3)
Konsumsi data¶
Snapshot ini di-konsumsi oleh:
- Konstanta Formula [WH] (Excess Working Hours → jam) dan [LMB] (Lembur → jam)
- Halaman Proses Gaji → Data Waktu Kerja (read-only view)
- Kalkulasi gaji periode tsb
6.6 Archive Periode¶
Setelah ≥ 4 periode hot di MySQL, periode tertua bisa di-archive.
Trigger¶
Manual: tombol "Archive" per periode di /admin/periode. Atau:
Auto: scheduler Redis trigger archive periode tertua bila > 4 hot.
Apa yang terjadi¶
- Ekspor
realisasi,jadwal_harian_karyawan,evaluasi_*ke Elasticsearch - Set
is_archived = true - Hapus data periode tsb dari MySQL (soft cleanup)
Hasil¶
Periode archived tidak muncul di list periode default. Bisa di-toggle filter is_archived=true untuk lihat. Data tetap accessible via:
- /admin/realisasi/history?periode_kode=X — search Elasticsearch
- Detail karyawan tab Realisasi → "Histori Realisasi"
6.7 Alur kerja typical (per periode)¶
Untuk periode "April 2026" (2026-04):
| Hari | Aksi |
|---|---|
| Akhir Maret | Tutup Bulan periode Maret → bikin Apr 2026 + generate jadwal |
| 1 April | Periode Apr 2026 status AKTIF |
| Sehari-hari April | Kelola pengajuan (cuti/ijin/lembur/SPD) — approve sebelum tutup bulan |
| Setiap hari (background) | Auto kalkulasi realisasi incremental |
| 30 April | Periode habis, status masih AKTIF (cut-off masih jalan) |
| 1-25 Mei | Periode AKTIF (bisa terima koreksi pengajuan/realisasi sampai cut-off) |
| 25 Mei (cut-off) | Lock realisasi |
| 26 Mei | Tutup Bulan Apr 2026 → snapshot evaluasi → Mei 2026 muncul |
| 26 Mei → | Lanjut ke Proses Gaji untuk periode Apr 2026 (lihat 07-proses-gaji.md) |
6.8 Tips¶
| Tips | Catatan |
|---|---|
| Generate jadwal harian SEKALI di awal periode | Re-generate akan replace seluruh data (kecuali yang manual override — TBD) |
| Approve semua pengajuan SEBELUM Tutup Bulan | Pengajuan PENDING tidak masuk evaluasi |
| Input jumlah_hadir_seharusnya yang konsisten | 1 angka global per periode — biasanya 22 (Senin-Jumat) atau 24 (Senin-Sabtu) |
| Cek snapshot evaluasi sebelum Kalkulasi Gaji | Data evaluasi salah → kalkulasi gaji salah |
| Archive periode tertua per quartal | Hindari MySQL bloat |
Langkah berikutnya¶
Setelah Tutup Bulan + evaluasi tersimpan, periode siap untuk Proses Gaji:
→ 7. Proses Gaji — review data + setup pengecualian + jalankan Kalkulasi.