Backend 6 min read

Mendesain Engine Payroll Enterprise: Otomatisasi Gaji, Pajak BPJS, dan EWA

Mengintip kalkulasi rumit di balik layar modul Payroll ForgeLenta untuk memproses ribuan slip gaji secara instan.

0 views

Menghitung penggajian (payroll) bulanan adalah salah satu pekerjaan paling menegangkan bagi departemen keuangan perusahaan. Ada begitu banyak komponen variabel yang harus dikalkulasi secara akurat untuk setiap karyawan: gaji pokok, tunjangan jabatan, lembur berdasarkan jam kerja aktif, potongan ketidakhadiran, denda surat peringatan (SP), pembayaran pinjaman karyawan, penarikan gaji lebih awal (Earned Wage Access), hingga kalkulasi iuran BPJS (Kesehatan dan Ketenagakerjaan).

Di dalam ForgeLenta HRIS, semua kalkulasi rumit ini ditangani oleh sebuah modul terpusat bernama Payroll Engine. Artikel ini akan membedah bagaimana kami mendesain struktur database, relasi entitas, dan logika backend penggajian agar dapat diproses secara instan dan bebas dari kesalahan (ACID Compliant).

Struktur Komponen Gaji

Sebelum masuk ke baris kode, kita harus merinci aliran komponen keuangan yang membentuk nilai akhir slip gaji bersih (Take Home Pay - THP):

$$\text{THP} = (\text{Gaji Pokok} + \text{Tunjangan} + \text{Uang Lembur}) - (\text{Potongan Kehadiran} + \text{Iuran BPJS Karyawan} + \text{Cicilan Pinjaman} + \text{Penarikan EWA})$$

Salah satu tantangan terbesar adalah iuran BPJS di Indonesia, di mana perusahaan menanggung sebagian persentase iuran dan karyawan menanggung sisanya melalui pemotongan gaji langsung.

Skema Database & Relasi

Kami mendesain tabel transaksi payroll yang menyimpan snapshot data saat penggajian dihitung, untuk mencegah perubahan data historis jika di kemudian hari ada kenaikan gaji karyawan atau perubahan profil jabatan.

// Menggambarkan schema konseptual dalam Drizzle ORM / SQL relasional
export const payrolls = sqliteTable('payrolls', {
  id: text('id').primaryKey(),
  employeeId: text('employee_id').notNull(),
  periodMonth: integer('period_month').notNull(), // 1 - 12
  periodYear: integer('period_year').notNull(),
  baseSalary: integer('base_salary').notNull(),
  overtimePay: integer('overtime_pay').notNull(),
  bpjsAllowance: integer('bpjs_allowance').notNull(), // Tunjangan BPJS dari perusahaan
  bpjsDeduction: integer('bpjs_deduction').notNull(), // Potongan BPJS dari gaji karyawan
  loanDeduction: integer('loan_deduction').notNull(),
  ewaDeduction: integer('ewa_deduction').notNull(),
  netSalary: integer('net_salary').notNull(), // Take home pay akhir
  status: text('status').default('draft') // draft, approved, paid
});

Logika Pemrosesan Batch (Laravel Service)

Untuk menghitung gaji secara massal bagi seluruh karyawan, kami membungkus logika tersebut di dalam sebuah Laravel Service yang berjalan di dalam database transaction untuk memastikan integritas data jika terjadi error di tengah jalan:

public function calculatePeriodPayroll($year, $month)
{
    DB::transaction(function () use ($year, $month) {
        $employees = Employee::where('status', 'active')->get();

        foreach ($employees as $employee) {
            // 1. Ambil data kehadiran dan hitung total lembur
            $attendanceSummary = $this->attendanceService->getSummary($employee->id, $year, $month);
            $overtimePay = $attendanceSummary->total_overtime_hours * $employee->overtime_rate_per_hour;

            // 2. Hitung BPJS (BPJS Kesehatan 4% perusahaan, 1% karyawan; BPJSTK JHT 3.7% perusahaan, 2% karyawan)
            $bpjsCalculator = new BpjsCalculator($employee->base_salary);
            $bpjsAllowance = $bpjsCalculator->calculateCompanyShare();
            $bpjsDeduction = $bpjsCalculator->calculateEmployeeShare();

            // 3. Ambil cicilan pinjaman aktif bulan ini
            $loanDeduction = LoanInstallment::where('employee_id', $employee->id)
                ->where('due_month', $month)
                ->where('due_year', $year)
                ->where('status', 'pending')
                ->sum('amount');

            // 4. Ambil akumulasi penarikan Earned Wage Access (EWA)
            $ewaDeduction = EwaWithdrawal::where('employee_id', $employee->id)
                ->where('period_month', $month)
                ->where('period_year', $year)
                ->where('status', 'paid')
                ->sum('amount');

            // 5. Kalkulasi Gaji Bersih (THP)
            $netSalary = ($employee->base_salary + $overtimePay + $bpjsAllowance) 
                         - ($bpjsDeduction + $loanDeduction + $ewaDeduction);

            // 6. Simpan Slip Gaji Baru
            Payroll::updateOrCreate(
                [
                    'employee_id' => $employee->id,
                    'period_month' => $month,
                    'period_year' => $year
                ],
                [
                    'base_salary' => $employee->base_salary,
                    'overtime_pay' => $overtimePay,
                    'bpjs_allowance' => $bpjsAllowance,
                    'bpjs_deduction' => $bpjsDeduction,
                    'loan_deduction' => $loanDeduction,
                    'ewa_deduction' => $ewaDeduction,
                    'net_salary' => $netSalary,
                    'status' => 'draft'
                ]
            );
        }
    });
}

Sinkronisasi dengan Earned Wage Access (EWA)

Earned Wage Access (EWA) adalah fitur kesejahteraan finansial yang memungkinkan karyawan menarik gaji yang sudah menjadi hak mereka sebelum tanggal gajian resmi. Misalnya, jika seorang karyawan telah bekerja selama 15 hari dari siklus 30 hari kerja, mereka dapat menarik hingga 50% dari total gaji bulanan mereka secara instan.

Agar EWA berjalan aman:

  • Sistem membatasi penarikan harian maksimum berdasarkan hari kerja aktif yang tercatat di log kehadiran presensi Face AI.
  • Setiap penarikan EWA berhasil dicatat sebagai transaksi "terbayar awal" dan otomatis ditarik sebagai pengurang (ewaDeduction) pada proses kalkulasi payroll di akhir bulan.

Kesimpulan

Membangun payroll engine skala enterprise membutuhkan ketelitian tinggi terhadap relasi data keuangan. Dengan menerapkan arsitektur transaksi database (ACID) dan meng snapshot parameter penggajian pada bulan berjalan, ForgeLenta berhasil menghadirkan payroll engine yang tangguh, aman, dan siap memproses ribuan slip gaji secara instan.