Lewati ke isi

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): - LEWATtanggal_selesai < today() - AKTIFtanggal_mulai ≤ today() ≤ tanggal_cut_off - AGENDAtanggal_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

  1. Klik "+ Tambah Periode"
  2. Modal:
  3. Tahun: dropdown (tahun ini ± 2)
  4. Bulan: dropdown 1-12
  5. 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)
  6. Tanggal Cut-Off: bisa di-override bila beda dengan default tenant
  7. 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) dan tanggal_cut_off
  • Tidak bisa edit tanggal_mulai/tanggal_selesai setelah ada jadwal_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.

  1. Pilih periode (mis. periode AGENDA yang baru dibuat)
  2. Klik tombol "Generate Jadwal Harian"
  3. Konfirmasi modal — proses dijalankan via Redis job (async untuk periode besar)
  4. Sistem return summary:
  5. karyawan_count
  6. hari_count
  7. rows_inserted
  8. hari_libur_count (tanggal yang di-skip karena libur)
  9. rows_with_shift vs rows_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:

  1. Klik "Download template Excel" di modal — sistem generate file .xlsx dengan:
  2. 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.
  3. Row 2 (label bulan): Apr/Mei dst. sebagai bantuan visual — tidak dibaca server.
  4. Row 3+: pre-fill semua NIK karyawan aktif, kolom tanggal kosong.
  5. Buka file di Excel, isi cell tanggal yang mau di-override:
  6. Kode shift (sesuai master Waktu Kerja, mis. N, NJ, 1) → set ke shift tsb
  7. Token libur: X, OFF, L, LIBUR, atau - → paksa libur (waktu_kerja kosong)
  8. Kosong → jangan override cell ini (biarkan hasil generate apa adanya)
  9. Save, kembali ke modal → klik "Pilih file Excel…" → klik Upload.
  10. Sistem return ringkasan:
  11. cells_overridden — jumlah cell yang berhasil di-apply
  12. rows_processed — jumlah karyawan yang diproses
  13. cells_skipped_out_of_range — cell yang di-skip karena tanggalnya di luar periode aktif
  14. errors — 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

  1. Pilih Periode (default: aktif)
  2. Klik "Kalkulasi Realisasi" (atau "Kalkulasi Tunggal" lalu pilih 1–10 karyawan)
  3. 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.
  4. 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)

  1. Insert ke evaluasi_kelebihan_hari_kerja per karyawan: (seharusnya, aktual, selisih)
  2. Insert ke evaluasi_excess_working_hours per karyawan: total menit excess (signed)
  3. Insert ke evaluasi_selisih_absensi per kelompok_kerja: agregat
  4. Insert ke evaluasi_akumulasi_lembur per karyawan: total durasi lembur DISETUJUI di periode tsb
  5. Bikin periode baru (pola tenant: bulan berikutnya)
  6. Generate jadwal harian periode baru (auto-trigger)

Hasil di UI

  • Status periode lama: AKTIFLEWAT
  • Periode baru muncul di list dengan status AGENDA atau AKTIF (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:

  1. Excess Working Hours per karyawan: total menit excess (positif = lebih, negatif = kurang)
  2. Kelebihan Hari Kerja per karyawan: hadir seharusnya, aktual, selisih
  3. Selisih Absensi per kelompok kerja: agregat menit excess
  4. 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

  1. Ekspor realisasi, jadwal_harian_karyawan, evaluasi_* ke Elasticsearch
  2. Set is_archived = true
  3. 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.