Files
2026-09-15 10:38:43 +07:00

67 lines
38 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
$sectionLabels=array('periods'=>array('hr_payroll','Daftar Periode Gaji','Kelola rentang kehadiran, proses payroll, jurnal, dan pembayaran dalam satu alur.'),'salaries'=>array('hr_payroll_salary','Master Gaji Karyawan','Atur pendapatan dan potongan berulang dengan histori tanggal efektif.'),'settings'=>array('hr_payroll_settings','Setting Payroll','Kelola komponen, template posisi, dan aturan kehadiran tanpa input berulang setiap bulan.'),'adjustments'=>array('hr_payroll_adjustments','Penyesuaian Periode','Catat bonus, rapel, insentif, atau potongan yang hanya berlaku pada satu periode.'));
$meta=$sectionLabels[$payroll_section];
$this->load->view('hr_payroll/_header',array('active_menu'=>$meta[0],'payroll_section'=>$payroll_section,'title'=>$meta[1],'subtitle'=>$meta[2]));
$csrf='<input type="hidden" name="'.$this->security->get_csrf_token_name().'" value="'.$this->security->get_csrf_hash().'">';
$methodLabels=array('fixed'=>'Nominal tetap','per_present_day'=>'Per hari hadir','per_absent_day'=>'Per hari tidak hadir','per_minute'=>'Per menit','per_hour'=>'Per jam','percentage_basic'=>'Persentase gaji pokok','formula'=>'Formula tervalidasi');
$statusLabels=array('draft'=>'Draft','reviewed'=>'Sudah Direview','approved'=>'Disetujui','final'=>'Final','posted'=>'Terposting','paid'=>'Dibayar','cancelled'=>'Dibatalkan');
?>
<?php foreach(array('success','error')as$flash):if($this->session->flashdata($flash)):?><div data-app-toast data-icon="<?=$flash==='success'?'success':'error'?>" data-message="<?=html_escape($this->session->flashdata($flash))?>"></div><?php endif;endforeach?>
<?php if(!$can_manage_all_salary):?><div class="alert alert-warning"><i class="bi bi-lock-fill me-1"></i> Tampilan ini tidak memuat nilai maupun total gaji terbatas. Akses tersebut memerlukan permission <b>Manage All Gaji</b>.</div><?php endif?>
<?php if(!$can_manage_allowances&&in_array($payroll_section,array('salaries','settings','adjustments'),true)):?><div class="alert alert-info"><i class="bi bi-shield-lock-fill me-1"></i> Akses pengelolaan dibatasi pada <b>Gaji Pokok dan Potongan</b>. Tunjangan, bonus, lembur, THR, dan pendapatan tambahan memerlukan permission <b>Kelola Tunjangan &amp; Bonus</b>.</div><?php endif?>
<?php if($payroll_section==='periods'):?>
<div class="hr-workflow-strip" aria-label="Tahapan payroll"><span><b>1</b>Buat Periode</span><span><b>2</b>Lock Kehadiran</span><span><b>3</b>Hitung Draft</span><span><b>4</b>Review HR</span><span><b>5</b>Approve & Final</span><span><b>6</b>Posting Jurnal</span><span><b>7</b>Pembayaran</span></div>
<?php if($permissions['create']):?><section class="card hr-panel mb-3"><div class="card-header bg-white"><b>Buat Periode Gaji</b><small class="d-block text-muted">Rentang kehadiran bebas melintasi bulan; periode pada perusahaan yang sama tidak boleh tumpang tindih.</small></div><div class="card-body"><form method="post" action="<?=base_url('hrpayroll/create_period')?>" class="row g-3"><?=$csrf?><div class="col-lg-3"><label class="form-label">Nama Periode</label><input class="form-control" name="period_name" placeholder="Contoh: Gaji September 2026" required></div><div class="col-md-3 col-lg-2"><label class="form-label">Awal Kehadiran</label><input type="date" class="form-control" name="attendance_start_date" required></div><div class="col-md-3 col-lg-2"><label class="form-label">Akhir Kehadiran</label><input type="date" class="form-control" name="attendance_end_date" required></div><div class="col-md-3 col-lg-2"><label class="form-label">Tanggal Bayar</label><input type="date" class="form-control" name="payment_date" required></div><div class="col-lg-3"><label class="form-label">Keterangan</label><div class="input-group"><input class="form-control" name="notes" placeholder="Opsional"><button class="btn btn-primary"><i class="bi bi-plus-lg"></i> Buat</button></div></div></form></div></section><?php endif?>
<section class="card hr-panel"><div class="card-header bg-white"><b>Daftar Periode Payroll</b><small class="d-block text-muted">Aksi hanya muncul sesuai status dan permission. Finalisasi tidak membuat jurnal; jurnal dibuat pada tahap Posting.</small></div><div class="table-responsive"><table class="table align-middle mb-0 payroll-table"><thead><tr><th>Periode</th><th>Lock Kehadiran</th><th>Status</th><th class="text-end">Karyawan</th><th class="text-end">Bruto</th><th class="text-end">Potongan</th><th class="text-end">Neto</th><th>Aksi Berikutnya</th></tr></thead><tbody>
<?php if(!$periods):?><tr><td colspan="8" class="text-center text-muted py-5">Belum ada periode payroll. Buat satu periode untuk memulai.</td></tr><?php endif;foreach($periods as$period):$start=$period->attendance_start_date?:$period->start_date;$end=$period->attendance_end_date?:$period->end_date;$locked=$period->attendance_lock_status==='locked';?>
<tr><td><b><?=html_escape($period->period_name)?></b><small class="d-block text-muted"><?=html_escape($start)?> s.d. <?=html_escape($end)?></small><small class="text-muted">Bayar: <?=html_escape($period->payment_date?:'-')?></small></td><td><span class="badge <?=$locked?'bg-success':'bg-secondary'?>"><?=$locked?'Terkunci':'Belum dikunci'?></span></td><td><span class="badge bg-<?=in_array($period->workflow_status,array('posted','paid'),true)?'success':($period->workflow_status==='draft'?'secondary':'warning')?>"><?=html_escape($statusLabels[$period->workflow_status]??$period->workflow_status)?></span></td><td class="text-end"><?=number_format($period->employee_count)?></td><td class="text-end">Rp <?=number_format($period->gross,2,',','.')?></td><td class="text-end">Rp <?=number_format($period->deductions,2,',','.')?></td><td class="text-end fw-bold">Rp <?=number_format($period->net,2,',','.')?></td><td><div class="payroll-actions"><button class="btn btn-sm btn-outline-primary payroll-detail" data-id="<?=$period->id?>"><i class="bi bi-eye"></i> Detail</button>
<?php if($permissions['post']&&$period->workflow_status==='draft'&&!$locked):?><form method="post" action="<?=base_url('hrpayroll/lock_attendance')?>" class="js-confirm" data-confirm-title="Kunci rentang kehadiran?" data-confirm-text="Absensi dalam rentang ini tidak dapat diubah setelah dikunci."><?=$csrf?><input type="hidden" name="period_id" value="<?=$period->id?>"><input type="hidden" name="notes" value="Validasi payroll <?=html_escape($period->period_name)?>"><button class="btn btn-sm btn-warning">Lock Kehadiran</button></form><?php endif?>
<?php if($permissions['create']&&$period->workflow_status==='draft'&&$locked):?><form method="post" action="<?=base_url('hrpayroll/calculate')?>" class="js-confirm" data-confirm-title="Hitung draft payroll?" data-confirm-text="Hasil Draft periode ini akan dibuat ulang tanpa menggandakan rincian."><?=$csrf?><input type="hidden" name="period_id" value="<?=$period->id?>"><button class="btn btn-sm btn-primary"><?=empty($period->calculated_at)?'Hitung Draft':'Hitung Ulang'?></button></form><?php endif?>
<?php if($permissions['approve']&&$period->workflow_status==='draft'&&!empty($period->calculated_at)):?><form method="post" action="<?=base_url('hrpayroll/workflow/'.$period->id.'/review')?>" class="js-confirm"><?=$csrf?><button class="btn btn-sm btn-warning">Review HR</button></form><?php endif?>
<?php if($permissions['approve']&&$period->workflow_status==='reviewed'):?><form method="post" action="<?=base_url('hrpayroll/workflow/'.$period->id.'/approve')?>" class="js-confirm"><?=$csrf?><button class="btn btn-sm btn-success">Approve</button></form><form method="post" action="<?=base_url('hrpayroll/workflow/'.$period->id.'/revert')?>" class="js-confirm"><?=$csrf?><button class="btn btn-sm btn-outline-secondary">Kembali ke Draft</button></form><?php endif?>
<?php if($permissions['approve']&&$period->workflow_status==='approved'):?><form method="post" action="<?=base_url('hrpayroll/workflow/'.$period->id.'/finalize')?>" class="js-confirm"><?=$csrf?><button class="btn btn-sm btn-dark">Finalisasi</button></form><form method="post" action="<?=base_url('hrpayroll/workflow/'.$period->id.'/revert')?>" class="js-confirm"><?=$csrf?><button class="btn btn-sm btn-outline-secondary">Kembali ke Draft</button></form><?php endif?>
<?php if($permissions['post']&&$period->workflow_status==='final'):?><form method="post" action="<?=base_url('hrpayroll/post/'.$period->id)?>" class="d-flex gap-1 js-confirm" data-confirm-title="Posting jurnal payroll?"><?=$csrf?><input class="form-control form-control-sm" type="date" name="date" value="<?=$period->payment_date?:date('Y-m-d')?>" required><button class="btn btn-sm btn-success">Posting</button></form><?php endif?>
<?php if($permissions['post']&&$period->workflow_status==='posted'):?><form method="post" action="<?=base_url('hrpayroll/pay/'.$period->id)?>" class="d-flex gap-1 js-confirm" data-confirm-title="Bayar payroll?"><?=$csrf?><input class="form-control form-control-sm" type="date" name="date" value="<?=$period->payment_date?:date('Y-m-d')?>" required><select class="form-select form-select-sm" name="cash_account_id" required><option value="">Kas/Bank</option><?php foreach($cash_accounts as$account):?><option value="<?=$account->id?>"><?=html_escape($account->code.' - '.$account->name)?></option><?php endforeach?></select><button class="btn btn-sm btn-success">Bayar</button></form><?php endif?>
<?php if($permissions['export']&&in_array($period->workflow_status,array('final','posted','paid'),true)):?><a class="btn btn-sm btn-outline-danger" href="<?=base_url('hrpayroll/export_pdf/'.$period->id)?>">PDF</a><a class="btn btn-sm btn-outline-success" href="<?=base_url('hrpayroll/export_excel/'.$period->id.'/general')?>">Excel</a><div class="dropdown"><button class="btn btn-sm btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown">Sumber Bank</button><div class="dropdown-menu"><a class="dropdown-item" href="<?=base_url('hrpayroll/export_excel/'.$period->id.'/bri')?>">BRI / QLola</a><a class="dropdown-item" href="<?=base_url('hrpayroll/export_excel/'.$period->id.'/bca')?>">BCA</a></div></div><?php endif?></div></td></tr><?php endforeach?>
</tbody></table></div></section>
<?php elseif($payroll_section==='salaries'):?>
<section class="card hr-panel"><div class="card-header bg-white"><b>Master Gaji Karyawan</b><small class="d-block text-muted">Estimasi hanya menjumlahkan komponen nominal tetap yang aktif saat ini; nilai final dihitung pada periode payroll.</small></div><div class="card-body border-bottom"><div class="row g-2"><div class="col-md-4"><label class="form-label">Departemen</label><select id="salaryDepartmentFilter" class="form-select"><option value="">Semua departemen</option><?php foreach($departments as$d):?><option><?=html_escape($d->department_name)?></option><?php endforeach?></select></div><div class="col-md-4"><label class="form-label">Posisi</label><select id="salaryPositionFilter" class="form-select"><option value="">Semua posisi</option><?php foreach($positions as$p):?><option><?=html_escape($p->position_name)?></option><?php endforeach?></select></div><div class="col-md-4"><label class="form-label">Status</label><select id="salaryStatusFilter" class="form-select"><option value="">Semua status</option><option>Aktif</option><option>Nonaktif</option></select></div></div></div><div class="table-responsive"><table id="salaryTable" class="table align-middle mb-0"><thead><tr><th>Karyawan</th><th>Departemen</th><th>Posisi</th><th>Status</th><th class="text-end">Pendapatan Tetap</th><th class="text-end">Potongan Tetap</th><th class="text-end">Estimasi Neto</th><th>Efektif</th><th>Aksi</th></tr></thead><tbody><?php foreach($salary_rows as$row):?><tr><td><b><?=html_escape($row->full_name)?></b><small class="d-block text-muted"><?=html_escape($row->employee_code)?></small></td><td><?=html_escape($row->department_name?:'-')?></td><td><?=html_escape($row->position_name?:'-')?></td><td><span class="badge <?=$row->is_active?'bg-success':'bg-secondary'?>"><?=$row->is_active?'Aktif':'Nonaktif'?></span><?php if($row->salary_restricted):?><span class="badge bg-danger">Gaji terbatas</span><?php endif?></td><td class="text-end">Rp <?=number_format($row->fixed_earnings,2,',','.')?></td><td class="text-end">Rp <?=number_format($row->fixed_deductions,2,',','.')?></td><td class="text-end fw-bold">Rp <?=number_format($row->estimated_net,2,',','.')?></td><td><?=html_escape($row->effective_from?:'-')?></td><td><?php if($permissions['update']):?><button class="btn btn-sm btn-outline-primary salary-manage" data-id="<?=$row->id?>"><i class="bi bi-sliders"></i> Atur Gaji</button><?php else:?><button class="btn btn-sm btn-outline-primary salary-manage" data-id="<?=$row->id?>">Detail</button><?php endif?></td></tr><?php endforeach?></tbody></table></div></section>
<?php elseif($payroll_section==='settings'):?>
<ul class="nav nav-pills payroll-setting-tabs mb-3" role="tablist"><li class="nav-item"><button class="nav-link active" data-bs-toggle="pill" data-bs-target="#componentsTab">Master Komponen</button></li><li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#positionsTab">Template Posisi</button></li><li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#attendanceRulesTab">Aturan Kehadiran</button></li><li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#generalTab">Pengaturan Umum</button></li></ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="componentsTab"><section class="card hr-panel"><div class="card-header bg-white"><b>Master Komponen</b><small class="d-block text-muted">Komponen tidak otomatis berlaku untuk semua karyawan.</small></div><?php if($permissions['update']):?><div class="card-body border-bottom"><form method="post" action="<?=base_url('hrpayroll/component')?>" class="row g-2"><?=$csrf?><div class="col-md-2"><label class="form-label">Kode</label><input class="form-control" name="code" required></div><div class="col-md-3"><label class="form-label">Nama</label><input class="form-control" name="name" required></div><div class="col-md-2"><label class="form-label">Jenis</label><select class="form-select" name="component_type"><option value="earning">Pendapatan</option><option value="deduction">Potongan</option><option value="employer_cost">Beban perusahaan</option></select></div><div class="col-md-2"><label class="form-label">Kategori</label><select class="form-select" name="category"><option value="basic">Gaji Pokok</option><option value="allowance">Tunjangan</option><option value="overtime">Lembur</option><option value="unpaid_leave">Cuti tak dibayar</option><option value="loan">Pinjaman</option><option value="bpjs">BPJS</option><option value="tax">Pajak</option><option value="thr">THR</option><option value="bonus">Bonus</option><option value="other">Lainnya</option></select></div><div class="col-md-3"><label class="form-label">Metode</label><select class="form-select" name="calculation_method"><?php foreach($methodLabels as$key=>$label):?><option value="<?=$key?>"><?=html_escape($label)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Nilai Default</label><input type="number" min="0" step="0.01" class="form-control" name="default_value" value="0"></div><div class="col-md-3"><label class="form-label">Formula</label><input class="form-control" name="formula_expression" placeholder="present_days * 25000"></div><div class="col-md-2"><label class="form-label">Akun Beban</label><select class="form-select searchable" name="expense_account_id"><option value="">Mapping umum</option><?php foreach($accounts as$a):?><option value="<?=$a->id?>"><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Akun Kewajiban</label><select class="form-select searchable" name="liability_account_id"><option value="">Mapping umum</option><?php foreach($accounts as$a):?><option value="<?=$a->id?>"><?=html_escape($a->kode_akun.' - '.$a->nama_akun)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Mulai Berlaku</label><input type="date" class="form-control" name="effective_from" value="<?=date('Y-m-d')?>"></div><div class="col-md-1 d-flex align-items-end"><label class="form-check mb-2"><input class="form-check-input" type="checkbox" name="is_active" value="1" checked> Aktif</label></div><div class="col-md-2 d-flex align-items-end justify-content-end"><button class="btn btn-primary">Simpan Komponen</button></div></form></div><?php endif?><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Kode</th><th>Komponen</th><th>Jenis</th><th>Metode</th><th class="text-end">Default</th><th>Mapping</th><th>Status</th></tr></thead><tbody><?php foreach($components as$c):?><tr><td><b><?=html_escape($c->code)?></b></td><td><?=html_escape($c->name)?></td><td><?=html_escape($c->component_type)?></td><td><?=html_escape($methodLabels[$c->calculation_method]??$c->calculation_method)?></td><td class="text-end">Rp <?=number_format($c->default_value??$c->fixed_amount,2,',','.')?></td><td><?=html_escape(($c->expense_account_id?:'mapping umum').' / '.($c->liability_account_id?:'mapping umum'))?></td><td><span class="badge <?=$c->is_active?'bg-success':'bg-secondary'?>"><?=$c->is_active?'Aktif':'Nonaktif'?></span></td></tr><?php endforeach?></tbody></table></div></section></div>
<div class="tab-pane fade" id="positionsTab"><section class="card hr-panel"><div class="card-header bg-white"><b>Template Posisi</b><small class="d-block text-muted">Override karyawan tetap memiliki prioritas di atas template posisi.</small></div><?php if($permissions['update']):?><div class="card-body border-bottom"><form method="post" action="<?=base_url('hrpayroll/save_position_line')?>" class="row g-2"><?=$csrf?><div class="col-md-3"><label class="form-label">Posisi</label><select class="form-select searchable" name="position_id" required><option value="">Pilih posisi</option><?php foreach($positions as$p):?><option value="<?=$p->id?>"><?=html_escape($p->position_name)?></option><?php endforeach?></select></div><div class="col-md-3"><label class="form-label">Komponen</label><select class="form-select searchable" name="component_id" required><option value="">Pilih komponen</option><?php foreach($manageable_components as$c):?><option value="<?=$c->id?>"><?=html_escape($c->code.' - '.$c->name)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Metode</label><select class="form-select" name="calculation_method"><?php foreach($methodLabels as$key=>$label):?><option value="<?=$key?>"><?=html_escape($label)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Nilai/Tarif</label><input type="number" min="0" step="0.01" class="form-control" name="amount_override" value="0"></div><div class="col-md-2"><label class="form-label">Mulai Berlaku</label><input type="date" class="form-control" name="effective_from" value="<?=date('Y-m-d')?>" required></div><div class="col-md-10"><input class="form-control" name="notes" placeholder="Keterangan template, opsional"></div><div class="col-md-2"><button class="btn btn-primary w-100">Tambah Template</button></div></form></div><?php endif?><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Posisi</th><th>Komponen</th><th>Metode</th><th class="text-end">Nilai</th><th>Efektif</th><th>Status</th></tr></thead><tbody><?php if(!$position_assignments):?><tr><td colspan="6" class="text-center text-muted py-4">Belum ada template posisi.</td></tr><?php endif;foreach($position_assignments as$x):?><tr><td><?=html_escape($x->position_name)?></td><td><?=html_escape($x->component_code.' - '.$x->component_name)?></td><td><?=html_escape($methodLabels[$x->calculation_method]??$x->calculation_method)?></td><td class="text-end">Rp <?=number_format($x->amount_override,2,',','.')?></td><td><?=html_escape($x->effective_from)?><?=!empty($x->effective_to)?' s.d. '.html_escape($x->effective_to):''?></td><td><span class="badge <?=$x->is_active?'bg-success':'bg-secondary'?>"><?=$x->is_active?'Aktif':'Nonaktif'?></span></td></tr><?php endforeach?></tbody></table></div></section></div>
<div class="tab-pane fade" id="attendanceRulesTab"><section class="card hr-panel"><div class="card-header bg-white"><b>Aturan Kehadiran</b><small class="d-block text-muted">Terlambat secara default tidak dipotong. Cuti tahunan, sakit, izin, dan hari libur tidak dianggap alpa.</small></div><?php if($permissions['update']):?><div class="card-body border-bottom"><form method="post" action="<?=base_url('hrpayroll/save_attendance_rule')?>" class="row g-2"><?=$csrf?><div class="col-md-3"><label class="form-label">Nama Aturan</label><input class="form-control" name="rule_name" required></div><div class="col-md-2"><label class="form-label">Kondisi</label><select class="form-select" name="condition_type"><option value="alpha">Alpa</option><option value="late">Terlambat</option><option value="early_leave">Pulang cepat</option><option value="unpaid">Cuti tidak dibayar</option><option value="missing_checkin">Tidak check-in</option><option value="missing_checkout">Tidak check-out</option></select></div><div class="col-md-2"><label class="form-label">Metode Potongan</label><select class="form-select" name="calculation_method"><option value="none">Tidak dipotong</option><option value="fixed">Nominal tetap</option><option value="per_minute">Per menit</option><option value="per_hour">Per jam</option><option value="per_day">Per hari</option><option value="percentage_daily">Persentase tarif harian</option><option value="formula">Formula</option></select></div><div class="col-md-2"><label class="form-label">Komponen Potongan</label><select class="form-select searchable" name="component_id"><option value="">Tidak ada</option><?php foreach($components as$c):if($c->component_type!=='deduction')continue;?><option value="<?=$c->id?>"><?=html_escape($c->name)?></option><?php endforeach?></select></div><div class="col-md-1"><label class="form-label">Toleransi</label><input type="number" min="0" class="form-control" name="tolerance_minutes" value="0"></div><div class="col-md-2"><label class="form-label">Tarif</label><input type="number" min="0" step="0.01" class="form-control" name="rate_value" value="0"></div><div class="col-md-2"><label class="form-label">Maksimum</label><input type="number" min="0" step="0.01" class="form-control" name="maximum_amount"></div><div class="col-md-3"><label class="form-label">Mulai Berlaku</label><input type="date" class="form-control" name="effective_from" value="<?=date('Y-m-d')?>" required></div><div class="col-md-4"><label class="form-label">Formula / Keterangan</label><input class="form-control" name="formula_expression" placeholder="condition_quantity * rule_rate"></div><div class="col-md-1 d-flex align-items-end"><label class="form-check mb-2"><input class="form-check-input" type="checkbox" name="is_active" value="1" checked> Aktif</label></div><div class="col-md-2 d-flex align-items-end"><button class="btn btn-primary w-100">Simpan Versi</button></div></form></div><?php endif?><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Aturan</th><th>Kondisi</th><th>Metode</th><th>Toleransi</th><th class="text-end">Tarif</th><th>Komponen</th><th>Efektif</th></tr></thead><tbody><?php foreach($attendance_rules as$r):?><tr><td><?=html_escape($r->rule_name)?></td><td><?=html_escape($r->condition_type)?></td><td><?=html_escape($r->calculation_method)?></td><td><?=number_format($r->tolerance_minutes)?> menit</td><td class="text-end"><?=number_format($r->rate_value,2,',','.')?></td><td><?=html_escape($r->component_name?:'Tidak memotong')?></td><td><?=html_escape($r->effective_from)?></td></tr><?php endforeach?></tbody></table></div></section></div>
<div class="tab-pane fade" id="generalTab"><section class="card hr-panel"><div class="card-header bg-white"><b>Pengaturan Umum Payroll</b><small class="d-block text-muted">Berlaku untuk perusahaan aktif dan tidak mengubah histori payroll lama.</small></div><div class="card-body"><div class="row g-3 mb-4"><div class="col-md-4"><div class="payroll-info-box"><b>Sumber Kehadiran</b><span>Rentang tanggal pada periode payroll</span></div></div><div class="col-md-4"><div class="payroll-info-box"><b>Prioritas Komponen</b><span>Override karyawan → template posisi → nilai default</span></div></div><div class="col-md-4"><div class="payroll-info-box"><b>Pengamanan</b><span>Snapshot, separation of duties, idempotency jurnal</span></div></div></div><?php if($permissions['update']):?><form method="post" action="<?=base_url('hrpayroll/save_settings')?>" class="row g-3"><?=$csrf?><div class="col-md-4"><label class="form-label">Pemisahan Tugas</label><label class="form-check form-switch"><input class="form-check-input" type="checkbox" name="separation_of_duties" value="1" <?=!$payroll_settings||$payroll_settings->separation_of_duties?'checked':''?>> Reviewer, approver, dan finalizer harus berbeda</label></div><div class="col-md-3"><label class="form-label">Tanggal Bayar Default</label><input type="number" min="1" max="31" class="form-control" name="default_payment_day" value="<?=html_escape($payroll_settings->default_payment_day??'')?>" placeholder="1–31"></div><div class="col-md-5"><label class="form-label">Catatan Export Bank</label><div class="input-group"><input class="form-control" name="bank_export_note" value="<?=html_escape($payroll_settings->bank_export_note??'')?>" placeholder="Instruksi internal sebelum upload"><button class="btn btn-primary">Simpan</button></div></div></form><?php endif?><p class="text-muted small mt-3 mb-0">Akun jurnal utama dikelola melalui System Account Mapping. Akun khusus dapat dipilih pada masing-masing komponen.</p></div></section></div>
</div>
<?php else:?>
<?php if($permissions['create']):?><section class="card hr-panel mb-3"><div class="card-header bg-white"><b>Tambah Penyesuaian Satu Periode</b><small class="d-block text-muted">Tidak membuat jurnal saat disimpan dan tidak berulang ke periode berikutnya.</small></div><div class="card-body"><form method="post" action="<?=base_url('hrpayroll/save_adjustment')?>" class="row g-2"><?=$csrf?><div class="col-md-3"><label class="form-label">Periode Draft</label><select class="form-select searchable" name="payroll_period_id" required><option value="">Pilih periode</option><?php foreach($periods as$p):if($p->workflow_status!=='draft')continue;?><option value="<?=$p->id?>"><?=html_escape($p->period_name)?></option><?php endforeach?></select></div><div class="col-md-3"><label class="form-label">Karyawan</label><select class="form-select searchable" name="employee_id" required><option value="">Pilih karyawan</option><?php foreach($employees as$e):?><option value="<?=$e->id?>"><?=html_escape($e->employee_code.' - '.$e->full_name)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Jenis</label><select class="form-select" name="adjustment_type"><?php if($can_manage_allowances):?><option value="earning">Pendapatan / Bonus</option><?php endif?><option value="deduction">Potongan</option></select></div><div class="col-md-2"><label class="form-label">Komponen</label><select class="form-select searchable" name="component_id" required><option value="">Pilih komponen</option><?php foreach($adjustment_components as$c):?><option value="<?=$c->id?>" data-type="<?=html_escape($c->component_type)?>"><?=html_escape($c->code.' - '.$c->name)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Nominal</label><input type="number" min="0.01" step="0.01" class="form-control" name="amount" required></div><div class="col-md-10"><label class="form-label">Keterangan</label><input class="form-control" name="notes" placeholder="Alasan bonus, rapel, koreksi, atau potongan" required></div><div class="col-md-2 d-flex align-items-end"><button class="btn btn-primary w-100">Simpan</button></div></form></div></section><?php endif?>
<section class="card hr-panel"><div class="card-header bg-white"><b>Daftar Penyesuaian</b></div><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Periode</th><th>Karyawan</th><th>Komponen</th><th>Jenis</th><th class="text-end">Nominal</th><th>Keterangan</th><th>Status</th><th>Aksi</th></tr></thead><tbody><?php if(!$adjustments):?><tr><td colspan="8" class="text-center text-muted py-5">Belum ada penyesuaian periode.</td></tr><?php endif;foreach($adjustments as$a):?><tr><td><?=html_escape($a->period_name)?></td><td><b><?=html_escape($a->full_name)?></b><small class="d-block text-muted"><?=html_escape($a->employee_code)?></small></td><td><?=html_escape($a->component_name)?></td><td><span class="badge <?=$a->adjustment_type==='earning'?'bg-success':'bg-warning text-dark'?>"><?=html_escape($a->adjustment_type==='earning'?'Pendapatan':'Potongan')?></span></td><td class="text-end">Rp <?=number_format($a->amount,2,',','.')?></td><td><?=html_escape($a->notes)?></td><td><?=html_escape($statusLabels[$a->workflow_status]??$a->workflow_status)?></td><td><?php if($permissions['update']&&!empty($a->can_manage)&&$a->workflow_status==='draft'):?><form method="post" action="<?=base_url('hrpayroll/deactivate_adjustment/'.$a->id)?>" class="js-confirm"><?=$csrf?><button class="btn btn-sm btn-outline-danger">Nonaktifkan</button></form><?php else:?>-<?php endif?></td></tr><?php endforeach?></tbody></table></div></section>
<?php endif?>
<div class="modal fade" id="payrollDetailModal" tabindex="-1"><div class="modal-dialog modal-xl modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><div><small class="text-muted">RINCIAN PERIODE</small><h5 class="modal-title" id="payrollDetailTitle">Payroll</h5></div><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="table-responsive"><table class="table align-middle"><thead><tr><th>Karyawan</th><th class="text-end">Hadir</th><th class="text-end">Alpa</th><th class="text-end">Lembur</th><th class="text-end">Bruto</th><th class="text-end">Potongan</th><th class="text-end">Neto</th><th>Slip</th></tr></thead><tbody id="payrollDetailRows"></tbody></table></div></div></div></div></div>
<div class="modal fade" id="salaryModal" tabindex="-1" data-bs-backdrop="static"><div class="modal-dialog modal-xl modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><div><small class="text-muted">STRUKTUR GAJI EFEKTIF</small><h5 class="modal-title" id="salaryModalTitle">Karyawan</h5><small id="salaryModalMeta" class="text-muted"></small></div><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="table-responsive mb-4"><table class="table align-middle"><thead><tr><th>Komponen</th><th>Jenis</th><th>Metode</th><th class="text-end">Nilai/Tarif</th><th>Efektif</th><th>Status</th><th>Aksi</th></tr></thead><tbody id="salaryLineRows"></tbody></table></div><?php if($permissions['update']):?><form id="salaryLineForm" data-ajax="true" class="row g-2"><?=$csrf?><input type="hidden" name="employee_id" id="salaryEmployeeId"><div class="col-md-3"><label class="form-label">Komponen</label><select class="form-select" name="component_id" required><option value="">Pilih komponen</option><?php foreach($manageable_components as$c):?><option value="<?=$c->id?>"><?=html_escape($c->code.' - '.$c->name)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Metode</label><select class="form-select" name="calculation_method"><?php foreach($methodLabels as$key=>$label):?><option value="<?=$key?>"><?=html_escape($label)?></option><?php endforeach?></select></div><div class="col-md-2"><label class="form-label">Nilai/Tarif</label><input type="number" min="0" step="0.01" class="form-control" name="amount_override" value="0" required></div><div class="col-md-2"><label class="form-label">Mulai Berlaku</label><input type="date" class="form-control" name="effective_from" value="<?=date('Y-m-d')?>" required></div><div class="col-md-2"><label class="form-label">Selesai (opsional)</label><input type="date" class="form-control" name="effective_to"></div><div class="col-md-1 d-flex align-items-end"><button type="submit" class="btn btn-primary w-100">Tambah</button></div><div class="col-md-6"><input class="form-control" name="formula_expression" placeholder="Formula bila metode Formula"></div><div class="col-md-6"><input class="form-control" name="notes" placeholder="Keterangan perubahan"></div></form><?php endif?></div></div></div></div>
<script>
$(function(){
const esc=v=>$('<div>').text(v??'').html(),money=v=>'Rp '+Number(v||0).toLocaleString('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2});
$('.searchable').select2({width:'100%'});
<?php if(!$can_manage_allowances):?>
$('#componentsTab select[name="component_type"] option').filter(function(){return this.value!=='deduction'}).remove();
$('#componentsTab select[name="category"] option').filter(function(){return !['unpaid_leave','loan','bpjs','tax','other'].includes(this.value)}).remove();
$('#generalTab form').remove();
<?php endif?>
const adjustmentForm=$('form[action$="hrpayroll/save_adjustment"]');
adjustmentForm.find('select[name="component_id"]').on('change',function(){const type=$(this).find(':selected').data('type');if(type)adjustmentForm.find('select[name="adjustment_type"]').val(type).trigger('change.select2')});
$(document).ajaxSuccess(function(_event,_xhr,settings,response){if(!String(settings.url||'').includes('hrpayroll/salary_detail/'))return;const lines=response?.data?.lines||response?.lines||[];$('#salaryLineRows tr').each(function(index){if(lines[index]&&!lines[index].can_manage)$(this).find('.deactivate-salary').remove()})});
if($('#salaryTable').length){const table=$('#salaryTable').DataTable({pageLength:25,order:[[0,'asc']],language:{search:'Cari:',lengthMenu:'Tampilkan _MENU_ data',info:'_START_–_END_ dari _TOTAL_',paginate:{previous:'Sebelumnya',next:'Berikutnya'}}});$('#salaryDepartmentFilter').on('change',function(){table.column(1).search(this.value).draw()});$('#salaryPositionFilter').on('change',function(){table.column(2).search(this.value).draw()});$('#salaryStatusFilter').on('change',function(){table.column(3).search(this.value).draw()})}
const detailModal=bootstrap.Modal.getOrCreateInstance('#payrollDetailModal');$('.payroll-detail').on('click',function(){$.getJSON('<?=base_url('hrpayroll/period_detail/')?>'+this.dataset.id).done(r=>{if(!r.status)return AppAlert.toast(r.message,'error');$('#payrollDetailTitle').text(r.period.period_name);$('#payrollDetailRows').html((r.rows||[]).map(x=>`<tr><td><b>${esc(x.full_name)}</b><small class="d-block text-muted">${esc(x.employee_code)}${x.salary_restricted==1?' · Gaji terbatas':''}</small></td><td class="text-end">${x.total_present}</td><td class="text-end">${x.total_alpha}</td><td class="text-end">${x.total_overtime_hours}</td><td class="text-end">${money(x.gross_salary)}</td><td class="text-end">${money(x.total_deductions)}</td><td class="text-end fw-bold">${money(x.total_salary)}</td><td>${['final','posted','paid'].includes(r.period.workflow_status)?`<a class="btn btn-sm btn-outline-primary" target="_blank" href="<?=base_url('hrpayroll/slip/')?>${x.id}">Slip</a>`:'-'}</td></tr>`).join('')||'<tr><td colspan="8" class="text-center text-muted">Belum ada rincian.</td></tr>');detailModal.show()}).fail(x=>AppAlert.toast(x.responseJSON?.message||'Rincian gagal dimuat.','error'))});
const salaryModal=bootstrap.Modal.getOrCreateInstance('#salaryModal');let currentEmployee=0;function loadSalary(id){currentEmployee=id;$('#salaryLineRows').html('<tr><td colspan="7" class="text-center py-4"><span class="spinner-border spinner-border-sm"></span> Memuat…</td></tr>');salaryModal.show();$.getJSON('<?=base_url('hrpayroll/salary_detail/')?>'+id).done(r=>{if(!r.status){salaryModal.hide();return AppAlert.toast(r.message,'error')}$('#salaryEmployeeId').val(id);$('#salaryModalTitle').text(r.data?.employee?.full_name||r.employee?.full_name||'Karyawan');const employee=r.data?.employee||r.employee,lines=r.data?.lines||r.lines||[];$('#salaryModalMeta').text((employee?.employee_code||'')+' · '+(employee?.department_name||'-')+' · '+(employee?.position_name||'-'));$('#salaryLineRows').html(lines.map(x=>`<tr><td><b>${esc(x.component_code)}</b><small class="d-block text-muted">${esc(x.component_name)}</small></td><td>${esc(x.component_type)}</td><td>${esc(x.calculation_method)}</td><td class="text-end">${money(x.amount_override)}</td><td>${esc(x.effective_from)}${x.effective_to?' s.d. '+esc(x.effective_to):''}</td><td><span class="badge ${x.is_active==1?'bg-success':'bg-secondary'}">${x.is_active==1?'Aktif':'Selesai'}</span></td><td>${x.is_active==1?`<button class="btn btn-sm btn-outline-danger deactivate-salary" data-id="${x.id}">Nonaktifkan</button>`:'-'}</td></tr>`).join('')||'<tr><td colspan="7" class="text-center text-muted py-4">Belum ada komponen gaji.</td></tr>')}).fail(x=>{salaryModal.hide();AppAlert.toast(x.responseJSON?.message||'Struktur gaji gagal dimuat.','error')})}
$('.salary-manage').on('click',function(){loadSalary(this.dataset.id)});$('#salaryLineForm').on('submit',function(e){e.preventDefault();const button=this.querySelector('button[type=submit]');button.disabled=true;$.ajax({url:'<?=base_url('hrpayroll/save_salary_line')?>',method:'POST',data:new FormData(this),processData:false,contentType:false,dataType:'json'}).done(r=>{if(!r.status)return AppAlert.toast(r.message,'error');AppAlert.toast(r.message,'success');this.reset();this.elements.employee_id.value=currentEmployee;loadSalary(currentEmployee)}).fail(x=>AppAlert.toast(x.responseJSON?.message||'Komponen gagal disimpan.','error')).always(()=>{button.disabled=false;if(window.AppLoading)AppLoading.hide()})});$(document).on('click','.deactivate-salary',function(){if(!confirm('Nonaktifkan komponen tanpa menghapus historinya?'))return;const form=new FormData();form.append('<?=$this->security->get_csrf_token_name()?>','<?=$this->security->get_csrf_hash()?>');$.ajax({url:'<?=base_url('hrpayroll/deactivate_salary_line/')?>'+this.dataset.id,method:'POST',data:form,processData:false,contentType:false,dataType:'json'}).done(r=>{AppAlert.toast(r.message,r.status?'success':'error');if(r.status)loadSalary(currentEmployee)}).fail(x=>AppAlert.toast(x.responseJSON?.message||'Komponen gagal dinonaktifkan.','error')).always(()=>{if(window.AppLoading)AppLoading.hide()})});
});
</script>