199 lines
15 KiB
PHP
199 lines
15 KiB
PHP
<?php
|
|
defined('BASEPATH') OR exit('No direct script access allowed');
|
|
|
|
class Leaverequests extends MY_Controller
|
|
{
|
|
private $leaveTypes = array('annual','sick','permit','maternity','unpaid');
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->load->library(array('CompanyContext','ApprovalService','AuditService','TransactionService','NumberingService'));
|
|
}
|
|
|
|
private function company(){return (int)$this->companycontext->id();}
|
|
private function uid(){return (int)$this->session->userdata('user_id');}
|
|
private function roleId()
|
|
{
|
|
$role=(int)$this->session->userdata('role_id');
|
|
if($role)return $role;
|
|
$user=$this->db->select('role_id')->get_where('users',array('id'=>$this->uid()))->row();
|
|
return $user?(int)$user->role_id:0;
|
|
}
|
|
private function can($action){return is_master_admin_user()||check_permission('employees',$action);}
|
|
private function permissions(){return array('create'=>$this->can('can_create'),'update'=>$this->can('can_update'),'approve'=>$this->can('can_approve'),'export'=>$this->can('can_export'));}
|
|
|
|
public function index()
|
|
{
|
|
$company=$this->company();
|
|
$employees=$this->db->select('id,employee_code,fingerprint_user_id,full_name')->where(array('company_id'=>$company,'is_active'=>1))->order_by('full_name')->get('k_employees')->result();
|
|
$counts=$this->workflowCounts();
|
|
$data=array('active_menu'=>'leave_requests','employees'=>$employees,'permissions'=>$this->permissions(),'counts'=>$counts);
|
|
$this->load->view('partials/header',$data);
|
|
$this->load->view('employees/leave_requests',$data);
|
|
$this->load->view('partials/footer');
|
|
}
|
|
|
|
public function get_data()
|
|
{
|
|
$status=trim((string)$this->input->get('status'));
|
|
$type=trim((string)$this->input->get('type'));
|
|
$employee=(int)$this->input->get('employee_id');
|
|
$this->db->select('lr.id,lr.request_no,lr.employee_id,lr.leave_type,lr.start_date,lr.end_date,lr.total_days,lr.reason,lr.approval_status,lr.workflow_status,lr.submitted_at,lr.approved_at,lr.rejected_at,lr.rejection_reason,lr.created_by,lr.submitted_by,e.full_name,e.employee_code,e.fingerprint_user_id,d.department_name,p.position_name,creator.nama creator_name,approver.nama approver_name,rejector.nama rejector_name')
|
|
->from('k_leave_requests lr')->join('k_employees e','e.id=lr.employee_id','left')
|
|
->join('k_departments d','d.id=e.department_id','left')->join('k_positions p','p.id=e.position_id','left')
|
|
->join('users creator','creator.id=lr.created_by','left')->join('users approver','approver.id=lr.approved_by','left')->join('users rejector','rejector.id=lr.rejected_by','left')
|
|
->where('lr.company_id',$this->company());
|
|
if(in_array($status,array('draft','submitted','approved','rejected','cancelled'),true))$this->db->where('lr.workflow_status',$status);
|
|
if(in_array($type,$this->leaveTypes,true))$this->db->where('lr.leave_type',$type);
|
|
if($employee>0)$this->db->where('lr.employee_id',$employee);
|
|
$rows=$this->db->order_by('lr.id','DESC')->get()->result();
|
|
return json_response(true,'Daftar pengajuan cuti.',array('data'=>$rows,'summary'=>$this->workflowCounts()));
|
|
}
|
|
|
|
public function detail($id)
|
|
{
|
|
$leave=$this->find((int)$id);
|
|
if(!$leave)return json_response(false,'Pengajuan cuti tidak ditemukan.',array(),404);
|
|
$history=array();
|
|
$history=$this->db->select('aa.action,aa.notes,aa.created_at,aa.step_order,u.nama user_name,aws.name step_name,ar.request_no approval_no')
|
|
->from('approval_actions aa')->join('approval_requests ar','ar.id=aa.request_id')
|
|
->join('users u','u.id=aa.user_id','left')->join('approval_workflow_steps aws','aws.workflow_id=ar.workflow_id AND aws.step_order=aa.step_order','left')
|
|
->where(array('ar.entity_type'=>'leave_request','ar.entity_id'=>$leave->id))->order_by('aa.id')->get()->result();
|
|
return json_response(true,'Detail pengajuan cuti.',array('leave'=>$leave,'history'=>$history,'permissions'=>$this->permissions()));
|
|
}
|
|
|
|
public function save()
|
|
{
|
|
if(strtoupper($this->input->method())!=='POST')return json_response(false,'Metode tidak diizinkan.',array(),405);
|
|
$id=(int)$this->input->post('id');
|
|
$this->requirePermission('employees',$id?'can_update':'can_create');
|
|
try{
|
|
$saved=$this->transactionservice->run(function()use($id){
|
|
$before=$id?$this->lock($id):null;
|
|
if($id&&!in_array($before->workflow_status,array('draft','rejected'),true))throw new BusinessException('Hanya Draft atau pengajuan yang ditolak yang dapat diperbaiki.');
|
|
$employeeId=(int)$this->input->post('employee_id');
|
|
$employee=$this->db->get_where('k_employees',array('id'=>$employeeId,'company_id'=>$this->company(),'is_active'=>1))->row();
|
|
if(!$employee)throw new BusinessException('Karyawan aktif tidak ditemukan.');
|
|
$type=trim((string)$this->input->post('leave_type'));
|
|
if(!in_array($type,$this->leaveTypes,true))throw new BusinessException('Jenis cuti atau izin tidak valid.');
|
|
$start=$this->validDate($this->input->post('start_date'),'Tanggal mulai');
|
|
$end=$this->validDate($this->input->post('end_date'),'Tanggal selesai');
|
|
if($end<$start)throw new BusinessException('Tanggal selesai tidak boleh sebelum tanggal mulai.');
|
|
$days=(int)((strtotime($end)-strtotime($start))/86400)+1;
|
|
if($days<1||$days>366)throw new BusinessException('Rentang cuti harus antara 1 sampai 366 hari.');
|
|
$reason=trim((string)$this->input->post('reason'));
|
|
if($reason==='')throw new BusinessException('Alasan pengajuan wajib diisi.');
|
|
if(mb_strlen($reason)>2000)throw new BusinessException('Alasan pengajuan maksimal 2.000 karakter.');
|
|
$overlap=$this->db->where(array('company_id'=>$this->company(),'employee_id'=>$employeeId))->where_in('workflow_status',array('submitted','approved'))->where('start_date <=',$end)->where('end_date >=',$start);
|
|
if($id)$overlap->where('id !=',$id);
|
|
if($overlap->count_all_results('k_leave_requests'))throw new BusinessException('Karyawan sudah mempunyai pengajuan aktif pada rentang tanggal tersebut.');
|
|
$row=array('employee_id'=>$employeeId,'leave_type'=>$type,'start_date'=>$start,'end_date'=>$end,'total_days'=>$days,'reason'=>$reason,'approval_status'=>'pending','workflow_status'=>'draft','rejection_reason'=>null,'rejected_by'=>null,'rejected_at'=>null,'updated_by'=>$this->uid(),'updated_at'=>date('Y-m-d H:i:s'));
|
|
if($id){$row['version']=(int)$before->version+1;$this->db->where(array('id'=>$id,'company_id'=>$this->company()))->update('k_leave_requests',$row);$leaveId=$id;$auditAction='update';}
|
|
else{$row['company_id']=$this->company();$row['request_no']=$this->numberingservice->next('leave_request',$start);$row['created_by']=$this->uid();$row['created_at']=date('Y-m-d H:i:s');$row['version']=1;$this->db->insert('k_leave_requests',$row);$leaveId=(int)$this->db->insert_id();$auditAction='create';}
|
|
$after=$this->find($leaveId);
|
|
$this->auditservice->record('hr','leave_request',$leaveId,$auditAction,$before,$after,$this->uid());
|
|
return $leaveId;
|
|
});
|
|
return json_response(true,$id?'Draft pengajuan berhasil diperbarui.':'Draft pengajuan berhasil dibuat.',array('id'=>$saved));
|
|
}catch(Throwable$e){return business_exception_response($e);}
|
|
}
|
|
|
|
public function submit($id)
|
|
{
|
|
$this->requirePermission('employees','can_create');
|
|
return $this->action(function()use($id){
|
|
$before=$this->lock((int)$id);
|
|
if($before->workflow_status!=='draft')throw new BusinessException('Hanya Draft yang dapat diajukan.');
|
|
$request=$this->approvalservice->create('hr','leave_request',$before->id,0,$this->uid(),array('company_id'=>$this->company(),'request_no'=>$before->request_no,'employee_id'=>$before->employee_id,'employee_name'=>$before->full_name,'leave_type'=>$before->leave_type,'start_date'=>$before->start_date,'end_date'=>$before->end_date,'total_days'=>$before->total_days));
|
|
$update=array('workflow_status'=>'submitted','approval_status'=>'pending','approval_request_id'=>(int)$request->id,'submitted_by'=>$this->uid(),'submitted_at'=>date('Y-m-d H:i:s'),'updated_by'=>$this->uid(),'updated_at'=>date('Y-m-d H:i:s'),'version'=>(int)$before->version+1);
|
|
$this->db->where(array('id'=>$before->id,'company_id'=>$this->company()))->update('k_leave_requests',$update);
|
|
$this->auditservice->record('hr','leave_request',$before->id,'submit',$before,$update,$this->uid());
|
|
return $before->id;
|
|
},'Pengajuan cuti berhasil dikirim untuk persetujuan.');
|
|
}
|
|
|
|
public function approve($id)
|
|
{
|
|
$this->requirePermission('employees','can_approve');
|
|
return $this->action(function()use($id){
|
|
$before=$this->lock((int)$id);
|
|
if($before->workflow_status!=='submitted')throw new BusinessException('Pengajuan tidak sedang menunggu persetujuan.');
|
|
$request=$this->approvalservice->pendingForEntity('leave_request',$before->id);
|
|
if(!$request)throw new BusinessException('Permintaan persetujuan tidak ditemukan.');
|
|
$result=$this->approvalservice->act($request->id,'approved',$this->uid(),$this->roleId(),trim((string)$this->input->post('notes'))?:null);
|
|
$update=array('updated_by'=>$this->uid(),'updated_at'=>date('Y-m-d H:i:s'),'version'=>(int)$before->version+1);
|
|
if($result->status==='approved'){$update['workflow_status']='approved';$update['approval_status']='approved';$update['approved_by']=$this->uid();$update['approved_at']=date('Y-m-d H:i:s');}
|
|
$this->db->where(array('id'=>$before->id,'company_id'=>$this->company()))->update('k_leave_requests',$update);
|
|
$this->auditservice->record('hr','leave_request',$before->id,'approve',$before,$update,$this->uid());
|
|
return $before->id;
|
|
},'Pengajuan cuti berhasil disetujui.');
|
|
}
|
|
|
|
public function reject($id)
|
|
{
|
|
$this->requirePermission('employees','can_approve');
|
|
return $this->action(function()use($id){
|
|
$reason=trim((string)$this->input->post('reason'));
|
|
if($reason==='')throw new BusinessException('Alasan penolakan wajib diisi.');
|
|
$before=$this->lock((int)$id);
|
|
if($before->workflow_status!=='submitted')throw new BusinessException('Pengajuan tidak sedang menunggu persetujuan.');
|
|
$request=$this->approvalservice->pendingForEntity('leave_request',$before->id);
|
|
if(!$request)throw new BusinessException('Permintaan persetujuan tidak ditemukan.');
|
|
$this->approvalservice->act($request->id,'rejected',$this->uid(),$this->roleId(),$reason);
|
|
$update=array('workflow_status'=>'rejected','approval_status'=>'rejected','rejected_by'=>$this->uid(),'rejected_at'=>date('Y-m-d H:i:s'),'rejection_reason'=>$reason,'updated_by'=>$this->uid(),'updated_at'=>date('Y-m-d H:i:s'),'version'=>(int)$before->version+1);
|
|
$this->db->where(array('id'=>$before->id,'company_id'=>$this->company()))->update('k_leave_requests',$update);
|
|
$this->auditservice->record('hr','leave_request',$before->id,'reject',$before,$update,$this->uid());
|
|
return $before->id;
|
|
},'Pengajuan cuti ditolak dan dapat diperbaiki kembali.');
|
|
}
|
|
|
|
public function cancel($id)
|
|
{
|
|
if(!$this->can('can_update')&&!$this->can('can_create'))return json_response(false,'Anda tidak memiliki izin untuk membatalkan Draft.',array(),403);
|
|
return $this->action(function()use($id){
|
|
$before=$this->lock((int)$id);
|
|
if(!in_array($before->workflow_status,array('draft','rejected'),true))throw new BusinessException('Hanya Draft atau pengajuan ditolak yang dapat dibatalkan.');
|
|
$update=array('workflow_status'=>'cancelled','cancelled_by'=>$this->uid(),'cancelled_at'=>date('Y-m-d H:i:s'),'updated_by'=>$this->uid(),'updated_at'=>date('Y-m-d H:i:s'),'version'=>(int)$before->version+1);
|
|
$this->db->where(array('id'=>$before->id,'company_id'=>$this->company()))->update('k_leave_requests',$update);
|
|
$this->auditservice->record('hr','leave_request',$before->id,'cancel',$before,$update,$this->uid());
|
|
return $before->id;
|
|
},'Draft pengajuan berhasil dibatalkan.');
|
|
}
|
|
|
|
private function action($callback,$message)
|
|
{
|
|
if(strtoupper($this->input->method())!=='POST')return json_response(false,'Metode tidak diizinkan.',array(),405);
|
|
try{$result=$this->transactionservice->run($callback);return json_response(true,$message,array('result'=>$result));}catch(Throwable$e){return business_exception_response($e);}
|
|
}
|
|
|
|
private function lock($id)
|
|
{
|
|
$row=$this->db->query('SELECT lr.*,e.full_name FROM k_leave_requests lr LEFT JOIN k_employees e ON e.id=lr.employee_id WHERE lr.id=? AND lr.company_id=? FOR UPDATE',array((int)$id,$this->company()))->row();
|
|
if(!$row)throw new BusinessException('Pengajuan cuti tidak ditemukan.');
|
|
return $row;
|
|
}
|
|
|
|
private function find($id)
|
|
{
|
|
return $this->db->select('lr.*,e.full_name,e.employee_code,e.fingerprint_user_id,d.department_name,p.position_name,ar.workflow_id,ar.request_no approval_no,ar.current_step,ar.status approval_request_status,creator.nama creator_name,submitter.nama submitter_name,approver.nama approver_name,rejector.nama rejector_name')
|
|
->from('k_leave_requests lr')->join('k_employees e','e.id=lr.employee_id','left')->join('k_departments d','d.id=e.department_id','left')->join('k_positions p','p.id=e.position_id','left')
|
|
->join('approval_requests ar','ar.id=lr.approval_request_id','left')->join('users creator','creator.id=lr.created_by','left')->join('users submitter','submitter.id=lr.submitted_by','left')->join('users approver','approver.id=lr.approved_by','left')->join('users rejector','rejector.id=lr.rejected_by','left')
|
|
->where(array('lr.id'=>(int)$id,'lr.company_id'=>$this->company()))->get()->row();
|
|
}
|
|
|
|
private function validDate($value,$label)
|
|
{
|
|
$value=trim((string)$value);$date=DateTime::createFromFormat('Y-m-d',$value);
|
|
if(!$date||$date->format('Y-m-d')!==$value)throw new BusinessException($label.' tidak valid.');
|
|
return $value;
|
|
}
|
|
|
|
private function workflowCounts()
|
|
{
|
|
$counts=array('all'=>0,'draft'=>0,'submitted'=>0,'approved'=>0,'rejected'=>0,'cancelled'=>0);
|
|
foreach($this->db->select('workflow_status,COUNT(*) total')->where('company_id',$this->company())->group_by('workflow_status')->get('k_leave_requests')->result() as $row){$counts[$row->workflow_status]=(int)$row->total;$counts['all']+=(int)$row->total;}
|
|
return $counts;
|
|
}
|
|
}
|