978 lines
39 KiB
PHP
978 lines
39 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Admin;
|
|
|
|
use App\Models\PegawaiModel;
|
|
use CodeIgniter\Database\BaseBuilder;
|
|
use CodeIgniter\Database\BaseConnection;
|
|
use Config\Database;
|
|
use Config\Services;
|
|
|
|
/**
|
|
* Logika API admin (/api/admin/*) — akses DB hanya dari sini, bukan dari controller web admin.
|
|
*/
|
|
class AdminApiService
|
|
{
|
|
protected BaseConnection $db;
|
|
|
|
public function __construct(?BaseConnection $db = null)
|
|
{
|
|
$this->db = $db ?? Database::connect();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null baris pegawai (tanpa password) atau null
|
|
*/
|
|
public function actorFromToken(string $token): ?array
|
|
{
|
|
if ($token === '') {
|
|
return null;
|
|
}
|
|
|
|
$row = $this->db->table('pegawai')->where('token', $token)->get()->getRowArray();
|
|
if ($row === null) {
|
|
return null;
|
|
}
|
|
|
|
unset($row['password']);
|
|
|
|
return $row;
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function references(?int $cabangKantorId = null): array
|
|
{
|
|
$kantor = $this->db->table('kantor')->orderBy('nama_kantor')->get()->getResultArray();
|
|
if ($cabangKantorId !== null && $cabangKantorId > 0) {
|
|
$kantor = array_values(array_filter(
|
|
$kantor,
|
|
static fn ($r) => (int) ($r['id_kantor'] ?? 0) === $cabangKantorId
|
|
));
|
|
}
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'jabatan' => $this->db->table('jabatan')->orderBy('nama_jabatan')->get()->getResultArray(),
|
|
'unit_kerja' => $this->db->table('unit_kerja')->orderBy('nama_unit_kerja')->get()->getResultArray(),
|
|
'golongan' => $this->db->table('golongan')->orderBy('nama_golongan')->get()->getResultArray(),
|
|
'kantor' => $kantor,
|
|
'jadwal' => $this->db->table('jadwal')->orderBy('nama_jadwal')->get()->getResultArray(),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function pegawaiList(int $page, int $perPage, string $search = '', ?int $cabangKantorId = null): array
|
|
{
|
|
$perPage = max(5, min(500, $perPage));
|
|
$page = max(1, $page);
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
$countB = $this->db->table('pegawai p')
|
|
->join('jabatan j', 'j.id_jabatan = p.jabatan', 'left')
|
|
->join('unit_kerja u', 'u.id_unit_kerja = p.unit_kerja', 'left')
|
|
->join('golongan g', 'g.id_golongan = p.golongan_pekerjaan', 'left')
|
|
->join('kantor k', 'k.id_kantor = p.kantor', 'left')
|
|
->join('jadwal jd', 'jd.id_jadwal = p.jadwal', 'left');
|
|
$this->applyCabangPegawaiAlias($countB, 'p', $cabangKantorId);
|
|
$this->applyPegawaiSearchFilter($countB, $search);
|
|
$total = (int) $countB->countAllResults();
|
|
|
|
$b = $this->db->table('pegawai p')
|
|
->select('p.id_pegawai, p.nip, p.nama_lengkap, p.jenis_kelamin, p.photo, p.email, p.jabatan, p.unit_kerja, p.golongan_pekerjaan, p.kantor, p.jadwal, p.status_kepegawaian, p.super_akses')
|
|
->select('j.nama_jabatan, u.nama_unit_kerja, g.nama_golongan, k.nama_kantor, jd.nama_jadwal')
|
|
->join('jabatan j', 'j.id_jabatan = p.jabatan', 'left')
|
|
->join('unit_kerja u', 'u.id_unit_kerja = p.unit_kerja', 'left')
|
|
->join('golongan g', 'g.id_golongan = p.golongan_pekerjaan', 'left')
|
|
->join('kantor k', 'k.id_kantor = p.kantor', 'left')
|
|
->join('jadwal jd', 'jd.id_jadwal = p.jadwal', 'left');
|
|
$this->applyCabangPegawaiAlias($b, 'p', $cabangKantorId);
|
|
$this->applyPegawaiSearchFilter($b, $search);
|
|
|
|
$rows = $b->orderBy('p.nama_lengkap', 'ASC')
|
|
->limit($perPage, $offset)
|
|
->get()
|
|
->getResultArray();
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'rows' => $rows,
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $perPage,
|
|
'total_page' => (int) ceil($total / $perPage),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function pegawaiShow(int $id, ?int $cabangKantorId = null): array
|
|
{
|
|
$row = $this->fetchPegawaiDetail($id);
|
|
if ($row === null) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
if (! $this->pegawaiRowInCabang($row, $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
|
|
return ['status' => 1, 'pesan' => 'OK', 'data' => $row];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, scalar|null> $input
|
|
*
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function pegawaiCreate(array $input, ?int $cabangKantorId = null): array
|
|
{
|
|
$validation = Services::validation();
|
|
$validation->setRules([
|
|
'nip' => 'required|max_length[50]|is_unique[pegawai.nip]',
|
|
'nama_lengkap' => 'required|max_length[50]',
|
|
'jenis_kelamin' => 'required|in_list[Pria,Wanita]',
|
|
'tempat_lahir' => 'permit_empty|max_length[100]',
|
|
'tanggal_lahir' => 'permit_empty|valid_date',
|
|
'email' => 'permit_empty|max_length[50]',
|
|
'jabatan' => 'required|integer',
|
|
'unit_kerja' => 'required|integer',
|
|
'golongan_pekerjaan' => 'required|integer',
|
|
'kantor' => 'required|integer',
|
|
'status_kepegawaian' => 'required|in_list[Kontrak,Pegawai Tetap]',
|
|
'tanggal_bergabung' => 'required|valid_date',
|
|
'jadwal' => 'required|integer',
|
|
'super_akses' => 'permit_empty|in_list[false,true]',
|
|
'photo' => 'permit_empty|max_length[255]',
|
|
'username' => 'required|max_length[50]|is_unique[pegawai.username]',
|
|
'password' => 'permit_empty|max_length[50]',
|
|
]);
|
|
|
|
if (! $validation->run($input)) {
|
|
return ['status' => 0, 'pesan' => implode(' ', $validation->getErrors())];
|
|
}
|
|
|
|
if ($cabangKantorId !== null && $cabangKantorId > 0 && (int) $input['kantor'] !== $cabangKantorId) {
|
|
return ['status' => 0, 'pesan' => 'Cabang pegawai harus sama dengan cabang Anda.'];
|
|
}
|
|
|
|
$nip = (string) $input['nip'];
|
|
$password = isset($input['password']) && (string) $input['password'] !== ''
|
|
? md5((string) $input['password'])
|
|
: md5($nip);
|
|
|
|
$model = new PegawaiModel();
|
|
$kantorVal = $cabangKantorId !== null && $cabangKantorId > 0 ? $cabangKantorId : (int) $input['kantor'];
|
|
$photoIn = trim((string) ($input['photo'] ?? ''));
|
|
$data = [
|
|
'nip' => $nip,
|
|
'nama_lengkap' => (string) $input['nama_lengkap'],
|
|
'jenis_kelamin' => (string) $input['jenis_kelamin'],
|
|
'tempat_lahir' => (string) ($input['tempat_lahir'] ?? ''),
|
|
'tanggal_lahir' => $this->normalizeDate($input['tanggal_lahir'] ?? null),
|
|
'photo' => $photoIn === '' || $photoIn === '-' ? '' : substr($photoIn, 0, 255),
|
|
'email' => (string) ($input['email'] ?? ''),
|
|
'jabatan' => (int) $input['jabatan'],
|
|
'unit_kerja' => (int) $input['unit_kerja'],
|
|
'golongan_pekerjaan' => (int) $input['golongan_pekerjaan'],
|
|
'kantor' => $kantorVal,
|
|
'status_kepegawaian' => (string) $input['status_kepegawaian'],
|
|
'tanggal_bergabung' => (string) $input['tanggal_bergabung'],
|
|
'jadwal' => (int) $input['jadwal'],
|
|
'super_akses' => (string) ($input['super_akses'] ?? 'false'),
|
|
'username' => (string) $input['username'],
|
|
'password' => $password,
|
|
'token' => '',
|
|
'last_login' => null,
|
|
];
|
|
|
|
if ($model->insert($data, true) === false) {
|
|
return ['status' => 0, 'pesan' => implode(' ', $model->errors())];
|
|
}
|
|
|
|
$id = (int) $model->getInsertID();
|
|
|
|
return ['status' => 1, 'pesan' => 'Data berhasil disimpan', 'data' => ['id_pegawai' => $id]];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, scalar|null> $input
|
|
*
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function pegawaiUpdate(int $id, array $input, ?int $cabangKantorId = null): array
|
|
{
|
|
$model = new PegawaiModel();
|
|
if ($model->find($id) === null) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
if (! $this->pegawaiIdAllowedForCabang($id, $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
|
|
$validation = Services::validation();
|
|
$validation->setRules([
|
|
'nip' => "required|max_length[50]|is_unique[pegawai.nip,id_pegawai,{$id}]",
|
|
'nama_lengkap' => 'required|max_length[50]',
|
|
'jenis_kelamin' => 'required|in_list[Pria,Wanita]',
|
|
'tempat_lahir' => 'permit_empty|max_length[100]',
|
|
'tanggal_lahir' => 'permit_empty|valid_date',
|
|
'email' => 'permit_empty|max_length[50]',
|
|
'jabatan' => 'required|integer',
|
|
'unit_kerja' => 'required|integer',
|
|
'golongan_pekerjaan' => 'required|integer',
|
|
'kantor' => 'required|integer',
|
|
'status_kepegawaian' => 'required|in_list[Kontrak,Pegawai Tetap]',
|
|
'tanggal_bergabung' => 'required|valid_date',
|
|
'jadwal' => 'required|integer',
|
|
'super_akses' => 'permit_empty|in_list[false,true]',
|
|
'photo' => 'permit_empty|max_length[255]',
|
|
'username' => "required|max_length[50]|is_unique[pegawai.username,id_pegawai,{$id}]",
|
|
'password' => 'permit_empty|max_length[50]',
|
|
]);
|
|
|
|
if (! $validation->run($input)) {
|
|
return ['status' => 0, 'pesan' => implode(' ', $validation->getErrors())];
|
|
}
|
|
|
|
if ($cabangKantorId !== null && $cabangKantorId > 0 && (int) $input['kantor'] !== $cabangKantorId) {
|
|
return ['status' => 0, 'pesan' => 'Cabang pegawai harus sama dengan cabang Anda.'];
|
|
}
|
|
|
|
$kantorVal = $cabangKantorId !== null && $cabangKantorId > 0 ? $cabangKantorId : (int) $input['kantor'];
|
|
$data = [
|
|
'nip' => (string) $input['nip'],
|
|
'nama_lengkap' => (string) $input['nama_lengkap'],
|
|
'jenis_kelamin' => (string) $input['jenis_kelamin'],
|
|
'tempat_lahir' => (string) ($input['tempat_lahir'] ?? ''),
|
|
'tanggal_lahir' => $this->normalizeDate($input['tanggal_lahir'] ?? null),
|
|
'email' => (string) ($input['email'] ?? ''),
|
|
'jabatan' => (int) $input['jabatan'],
|
|
'unit_kerja' => (int) $input['unit_kerja'],
|
|
'golongan_pekerjaan' => (int) $input['golongan_pekerjaan'],
|
|
'kantor' => $kantorVal,
|
|
'status_kepegawaian' => (string) $input['status_kepegawaian'],
|
|
'tanggal_bergabung' => (string) $input['tanggal_bergabung'],
|
|
'jadwal' => (int) $input['jadwal'],
|
|
'super_akses' => (string) ($input['super_akses'] ?? 'false'),
|
|
'username' => (string) $input['username'],
|
|
];
|
|
|
|
if (array_key_exists('photo', $input)) {
|
|
$p = trim((string) $input['photo']);
|
|
$data['photo'] = $p === '' || $p === '-' ? '' : substr($p, 0, 255);
|
|
}
|
|
|
|
if (isset($input['password']) && (string) $input['password'] !== '') {
|
|
$data['password'] = md5((string) $input['password']);
|
|
}
|
|
|
|
if ($model->update($id, $data) === false) {
|
|
return ['status' => 0, 'pesan' => implode(' ', $model->errors())];
|
|
}
|
|
|
|
return ['status' => 1, 'pesan' => 'Data berhasil diperbarui'];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string}
|
|
*/
|
|
public function pegawaiDelete(int $id, ?int $cabangKantorId = null): array
|
|
{
|
|
$model = new PegawaiModel();
|
|
if ($model->find($id) === null) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
if (! $this->pegawaiIdAllowedForCabang($id, $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
|
|
try {
|
|
$model->delete($id, true);
|
|
} catch (\Throwable $e) {
|
|
return ['status' => 0, 'pesan' => 'Tidak dapat menghapus pegawai (kemungkinan masih direferensikan data lain).'];
|
|
}
|
|
|
|
return ['status' => 1, 'pesan' => 'Data pegawai telah dihapus'];
|
|
}
|
|
|
|
/**
|
|
* Setel ulang password ke md5(NIP) dan kosongkan token (seperti CI3 admin).
|
|
*
|
|
* @return array{status: int, pesan: string}
|
|
*/
|
|
public function pegawaiResetPassword(int $id, ?int $cabangKantorId = null): array
|
|
{
|
|
$row = $this->db->table('pegawai')->select('id_pegawai, nip')->where('id_pegawai', $id)->get()->getRowArray();
|
|
if ($row === null) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
if (! $this->pegawaiIdAllowedForCabang($id, $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
|
|
$this->db->table('pegawai')->where('id_pegawai', $id)->update([
|
|
'password' => md5((string) $row['nip']),
|
|
'token' => '',
|
|
]);
|
|
|
|
return ['status' => 1, 'pesan' => 'Password direset ke NIP (hash MD5) dan token dikosongkan.'];
|
|
}
|
|
|
|
/**
|
|
* Batasi query ke baris presensi yang sudah ada minimal satu rekam nyata.
|
|
* Baris "kosong" dari API mobile presensi_today (pegawai buka app → insert stub) tidak ikut.
|
|
*/
|
|
protected function applyPresensiHasMinimalRekam(BaseBuilder $builder): void
|
|
{
|
|
$builder->where(
|
|
'(
|
|
(pr.jam_masuk IS NOT NULL AND TRIM(CAST(pr.jam_masuk AS CHAR)) NOT IN (\'\',\'00:00\',\'00:00:00\'))
|
|
OR (pr.jam_pulang IS NOT NULL AND TRIM(CAST(pr.jam_pulang AS CHAR)) NOT IN (\'\',\'00:00\',\'00:00:00\'))
|
|
OR (pr.mulai_istirahat IS NOT NULL AND TRIM(CAST(pr.mulai_istirahat AS CHAR)) NOT IN (\'\',\'00:00\',\'00:00:00\'))
|
|
OR (pr.beres_istirahat IS NOT NULL AND TRIM(CAST(pr.beres_istirahat AS CHAR)) NOT IN (\'\',\'00:00\',\'00:00:00\'))
|
|
OR (pr.photo_masuk IS NOT NULL AND TRIM(CAST(pr.photo_masuk AS CHAR)) <> \'\')
|
|
OR (pr.photo_pulang IS NOT NULL AND TRIM(CAST(pr.photo_pulang AS CHAR)) <> \'\')
|
|
)',
|
|
null,
|
|
false
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Daftar pegawai (sesuai cabang) yang hari ini belum presensi minimal dan bukan cuti Approve.
|
|
*
|
|
* @return list<array{id_pegawai: int, nama_lengkap: string, nip: string}>
|
|
*/
|
|
private function belumRekamPegawaiHariIni(string $tanggalHariIni, ?int $cabangKantorId): array
|
|
{
|
|
$hadPresensi = $this->db->table('presensi pr')
|
|
->select('pr.pegawai')
|
|
->distinct()
|
|
->join('pegawai pg', 'pg.id_pegawai = pr.pegawai', 'inner')
|
|
->where('pr.tanggal', $tanggalHariIni);
|
|
$this->applyPresensiHasMinimalRekam($hadPresensi);
|
|
$this->applyCabangPegawaiAlias($hadPresensi, 'pg', $cabangKantorId);
|
|
$idsP = [];
|
|
foreach ($hadPresensi->get()->getResultArray() as $r) {
|
|
$id = (int) ($r['pegawai'] ?? 0);
|
|
if ($id > 0) {
|
|
$idsP[] = $id;
|
|
}
|
|
}
|
|
|
|
$hadCuti = $this->db->table('cuti c')
|
|
->select('c.pegawai')
|
|
->distinct()
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'inner')
|
|
->where('c.tanggal_cuti', $tanggalHariIni)
|
|
->where('c.status_cuti', 'Approve');
|
|
$this->applyCabangPegawaiAlias($hadCuti, 'p', $cabangKantorId);
|
|
$idsC = [];
|
|
foreach ($hadCuti->get()->getResultArray() as $r) {
|
|
$id = (int) ($r['pegawai'] ?? 0);
|
|
if ($id > 0) {
|
|
$idsC[] = $id;
|
|
}
|
|
}
|
|
|
|
$exclude = array_values(array_unique(array_merge($idsP, $idsC)));
|
|
|
|
$qb = $this->db->table('pegawai p')
|
|
->select('p.id_pegawai, p.nama_lengkap, p.nip')
|
|
->orderBy('p.nama_lengkap', 'ASC');
|
|
$this->applyCabangPegawaiAlias($qb, 'p', $cabangKantorId);
|
|
if ($exclude !== []) {
|
|
$qb->whereNotIn('p.id_pegawai', $exclude);
|
|
}
|
|
|
|
$out = [];
|
|
foreach ($qb->get()->getResultArray() as $row) {
|
|
if (! is_array($row)) {
|
|
continue;
|
|
}
|
|
$out[] = [
|
|
'id_pegawai' => (int) ($row['id_pegawai'] ?? 0),
|
|
'nama_lengkap' => (string) ($row['nama_lengkap'] ?? ''),
|
|
'nip' => (string) ($row['nip'] ?? ''),
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function presensiList(string $tanggalDari, string $tanggalSampai, int $page, int $perPage, string $search = '', ?int $cabangKantorId = null): array
|
|
{
|
|
$perPage = max(5, min(200, $perPage));
|
|
$page = max(1, $page);
|
|
$offset = ($page - 1) * $perPage;
|
|
$search = trim($search);
|
|
|
|
$countB = $this->db->table('presensi pr')
|
|
->join('pegawai pg', 'pg.id_pegawai = pr.pegawai', 'left')
|
|
->where('pr.tanggal >=', $tanggalDari)
|
|
->where('pr.tanggal <=', $tanggalSampai);
|
|
$this->applyPresensiHasMinimalRekam($countB);
|
|
$this->applyCabangPegawaiAlias($countB, 'pg', $cabangKantorId);
|
|
if ($search !== '') {
|
|
$countB->groupStart()
|
|
->like('pg.nama_lengkap', $search)
|
|
->orLike('pg.nip', $search)
|
|
->groupEnd();
|
|
}
|
|
$total = (int) $countB->countAllResults();
|
|
|
|
$b = $this->db->table('presensi pr')
|
|
->select('pr.*, pg.nama_lengkap, pg.nip')
|
|
->join('pegawai pg', 'pg.id_pegawai = pr.pegawai', 'left')
|
|
->where('pr.tanggal >=', $tanggalDari)
|
|
->where('pr.tanggal <=', $tanggalSampai);
|
|
$this->applyPresensiHasMinimalRekam($b);
|
|
$this->applyCabangPegawaiAlias($b, 'pg', $cabangKantorId);
|
|
if ($search !== '') {
|
|
$b->groupStart()
|
|
->like('pg.nama_lengkap', $search)
|
|
->orLike('pg.nip', $search)
|
|
->groupEnd();
|
|
}
|
|
|
|
$rows = $b->orderBy('pr.tanggal', 'DESC')
|
|
->orderBy('pg.nama_lengkap', 'ASC')
|
|
->limit($perPage, $offset)
|
|
->get()
|
|
->getResultArray();
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'rows' => $rows,
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $perPage,
|
|
'total_page' => (int) ceil($total / $perPage),
|
|
'tanggal_dari' => $tanggalDari,
|
|
'tanggal_sampai' => $tanggalSampai,
|
|
'q' => $search,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function presensiShow(int $id, ?int $cabangKantorId = null): array
|
|
{
|
|
$row = $this->db->table('presensi pr')
|
|
->select('pr.*, pg.nama_lengkap, pg.nip, pg.email, pg.kantor as pegawai_kantor')
|
|
->join('pegawai pg', 'pg.id_pegawai = pr.pegawai', 'left')
|
|
->where('pr.id_presensi', $id)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if ($row === null) {
|
|
return ['status' => 0, 'pesan' => 'Data presensi tidak ditemukan'];
|
|
}
|
|
if (! $this->pegawaiRowInCabang(['kantor' => $row['pegawai_kantor'] ?? null], $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data presensi tidak ditemukan'];
|
|
}
|
|
unset($row['pegawai_kantor']);
|
|
|
|
return ['status' => 1, 'pesan' => 'OK', 'data' => $row];
|
|
}
|
|
|
|
/**
|
|
* Ringkasan seperti dashboard admin CI3 (Home): pegawai, presensi & cuti per rentang tanggal.
|
|
*
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function laporanSummary(string $tanggalDari, string $tanggalSampai, ?int $cabangKantorId = null): array
|
|
{
|
|
$pegawaiBase = $this->db->table('pegawai');
|
|
$this->applyCabangPegawaiAlias($pegawaiBase, 'pegawai', $cabangKantorId);
|
|
$totalPegawai = (int) $pegawaiBase->countAllResults();
|
|
|
|
$presensiQb = $this->db->table('presensi pr')
|
|
->join('pegawai pg', 'pg.id_pegawai = pr.pegawai', 'inner')
|
|
->where('pr.tanggal >=', $tanggalDari)
|
|
->where('pr.tanggal <=', $tanggalSampai);
|
|
$this->applyPresensiHasMinimalRekam($presensiQb);
|
|
$this->applyCabangPegawaiAlias($presensiQb, 'pg', $cabangKantorId);
|
|
$presensiQ = (int) $presensiQb->countAllResults();
|
|
|
|
$cutiQb = $this->db->table('cuti c')
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'inner')
|
|
->where('c.tanggal_cuti >=', $tanggalDari)
|
|
->where('c.tanggal_cuti <=', $tanggalSampai)
|
|
->where('c.status_cuti', 'Approve');
|
|
$this->applyCabangPegawaiAlias($cutiQb, 'p', $cabangKantorId);
|
|
$cutiQ = (int) $cutiQb->countAllResults();
|
|
|
|
$hariIni = date('Y-m-d');
|
|
$ph = $this->db->table('presensi pr')
|
|
->join('pegawai pg', 'pg.id_pegawai = pr.pegawai', 'inner')
|
|
->where('pr.tanggal', $hariIni);
|
|
$this->applyPresensiHasMinimalRekam($ph);
|
|
$this->applyCabangPegawaiAlias($ph, 'pg', $cabangKantorId);
|
|
$presensiHariIni = (int) $ph->countAllResults();
|
|
|
|
$ch = $this->db->table('cuti c')
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'inner')
|
|
->where('c.tanggal_cuti', $hariIni)
|
|
->where('c.status_cuti', 'Approve');
|
|
$this->applyCabangPegawaiAlias($ch, 'p', $cabangKantorId);
|
|
$cutiHariIni = (int) $ch->countAllResults();
|
|
|
|
$belumRekam = max(0, $totalPegawai - $presensiHariIni - $cutiHariIni);
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'total_pegawai' => $totalPegawai,
|
|
'rentang' => ['dari' => $tanggalDari, 'sampai' => $tanggalSampai],
|
|
'presensi_rekam' => $presensiQ,
|
|
'cuti_approve' => $cutiQ,
|
|
'hari_ini' => $hariIni,
|
|
'presensi_hari_ini' => $presensiHariIni,
|
|
'cuti_hari_ini' => $cutiHariIni,
|
|
'belum_rekam_hari_ini' => $belumRekam,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Dashboard beranda admin — selaras `modules/admin/controllers/Home.php` CI3.
|
|
*
|
|
* @param int|null $soloPegawaiId Bila diisi (login panel sebagai pegawai saja), angka & cuti
|
|
* hanya untuk pegawai tersebut — bukan agregat seluruh perusahaan.
|
|
*
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function dashboardHome(?int $cabangKantorId = null, ?int $soloPegawaiId = null): array
|
|
{
|
|
$tanggalHariIni = date('Y-m-d');
|
|
|
|
if ($soloPegawaiId !== null && $soloPegawaiId > 0) {
|
|
return $this->dashboardHomeSoloPegawai($tanggalHariIni, $soloPegawaiId);
|
|
}
|
|
|
|
$pb = $this->db->table('pegawai');
|
|
$this->applyCabangPegawaiAlias($pb, 'pegawai', $cabangKantorId);
|
|
$totalPegawai = (int) $pb->countAllResults();
|
|
|
|
$pbL = $this->db->table('pegawai')->where('jenis_kelamin', 'Pria');
|
|
$this->applyCabangPegawaiAlias($pbL, 'pegawai', $cabangKantorId);
|
|
$pegawaiLaki = (int) $pbL->countAllResults();
|
|
|
|
$pbP = $this->db->table('pegawai')->where('jenis_kelamin', 'Wanita');
|
|
$this->applyCabangPegawaiAlias($pbP, 'pegawai', $cabangKantorId);
|
|
$pegawaiPerempuan = (int) $pbP->countAllResults();
|
|
|
|
$ph = $this->db->table('presensi pr')
|
|
->join('pegawai pg', 'pg.id_pegawai = pr.pegawai', 'inner')
|
|
->where('pr.tanggal', $tanggalHariIni);
|
|
$this->applyPresensiHasMinimalRekam($ph);
|
|
$this->applyCabangPegawaiAlias($ph, 'pg', $cabangKantorId);
|
|
$presensiHariIni = (int) $ph->countAllResults();
|
|
|
|
$ch = $this->db->table('cuti c')
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'inner')
|
|
->where('c.tanggal_cuti', $tanggalHariIni)
|
|
->where('c.status_cuti', 'Approve');
|
|
$this->applyCabangPegawaiAlias($ch, 'p', $cabangKantorId);
|
|
$cutiHariIni = (int) $ch->countAllResults();
|
|
|
|
$belumRekam = max(0, $totalPegawai - $presensiHariIni - $cutiHariIni);
|
|
|
|
$belumRekamPegawai = $this->belumRekamPegawaiHariIni($tanggalHariIni, $cabangKantorId);
|
|
|
|
$persenLaki = $totalPegawai > 0 ? round(($pegawaiLaki / $totalPegawai) * 100, 1) : 0.0;
|
|
$persenPerempuan = $totalPegawai > 0 ? round(($pegawaiPerempuan / $totalPegawai) * 100, 1) : 0.0;
|
|
$persenPresensi = $totalPegawai > 0 ? round(($presensiHariIni / $totalPegawai) * 100, 1) : 0.0;
|
|
$persenCuti = $totalPegawai > 0 ? round(($cutiHariIni / $totalPegawai) * 100, 1) : 0.0;
|
|
$persenBelumRekam = $totalPegawai > 0 ? round(($belumRekam / $totalPegawai) * 100, 1) : 0.0;
|
|
|
|
$permohonanCutiB = $this->db->table('cuti c')
|
|
->select('c.id_cuti, c.pegawai, DATE_FORMAT(c.tanggal_cuti, \'%Y-%m-%d\') AS tanggal_cuti, c.tipe_cuti, c.alasan_cuti, c.status_cuti, p.nama_lengkap, p.nip', false)
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'left')
|
|
->where('c.status_cuti', 'Waiting');
|
|
$this->applyCabangPegawaiAlias($permohonanCutiB, 'p', $cabangKantorId);
|
|
$permohonanCuti = $permohonanCutiB->orderBy('c.tanggal_cuti', 'ASC')
|
|
->limit(5)
|
|
->get()
|
|
->getResultArray();
|
|
$permohonanCuti = array_map(
|
|
fn ($r): array => is_array($r) ? $this->normalizeCutiRowArrayForApi($r) : [],
|
|
$permohonanCuti,
|
|
);
|
|
|
|
$tpc = $this->db->table('cuti c')
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'inner')
|
|
->where('c.status_cuti', 'Waiting');
|
|
$this->applyCabangPegawaiAlias($tpc, 'p', $cabangKantorId);
|
|
$totalPermohonanCuti = (int) $tpc->countAllResults();
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'tanggal_hari_ini' => $tanggalHariIni,
|
|
'total_pegawai' => $totalPegawai,
|
|
'pegawai_laki' => $pegawaiLaki,
|
|
'pegawai_perempuan' => $pegawaiPerempuan,
|
|
'presensi_hari_ini' => $presensiHariIni,
|
|
'cuti_hari_ini' => $cutiHariIni,
|
|
'belum_rekam' => $belumRekam,
|
|
'persen_laki' => $persenLaki,
|
|
'persen_perempuan' => $persenPerempuan,
|
|
'persen_presensi' => $persenPresensi,
|
|
'persen_cuti' => $persenCuti,
|
|
'persen_belum_rekam' => $persenBelumRekam,
|
|
'permohonan_cuti' => $permohonanCuti,
|
|
'total_permohonan_cuti' => $totalPermohonanCuti,
|
|
'belum_rekam_pegawai' => $belumRekamPegawai,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Ringkasan beranda untuk satu pegawai (sesi login mobile / non-Ion).
|
|
*
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
private function dashboardHomeSoloPegawai(string $tanggalHariIni, int $pegawaiId): array
|
|
{
|
|
$row = $this->db->table('pegawai')->select('id_pegawai, jenis_kelamin')->where('id_pegawai', $pegawaiId)->get()->getRowArray();
|
|
if ($row === null) {
|
|
return ['status' => 0, 'pesan' => 'Data pegawai tidak ditemukan'];
|
|
}
|
|
|
|
$jk = (string) ($row['jenis_kelamin'] ?? '');
|
|
$laki = $jk === 'Pria' ? 1 : 0;
|
|
$wanit = $jk === 'Wanita' ? 1 : 0;
|
|
|
|
$ph = $this->db->table('presensi pr')
|
|
->where('pr.pegawai', $pegawaiId)
|
|
->where('pr.tanggal', $tanggalHariIni);
|
|
$this->applyPresensiHasMinimalRekam($ph);
|
|
$presensiHariIni = (int) $ph->countAllResults();
|
|
|
|
$ch = $this->db->table('cuti c')
|
|
->where('c.pegawai', $pegawaiId)
|
|
->where('c.tanggal_cuti', $tanggalHariIni)
|
|
->where('c.status_cuti', 'Approve');
|
|
$cutiHariIni = (int) $ch->countAllResults();
|
|
|
|
$belumRekam = max(0, 1 - $presensiHariIni - $cutiHariIni);
|
|
|
|
$permohonanCutiB = $this->db->table('cuti c')
|
|
->select('c.id_cuti, c.pegawai, DATE_FORMAT(c.tanggal_cuti, \'%Y-%m-%d\') AS tanggal_cuti, c.tipe_cuti, c.alasan_cuti, c.status_cuti, p.nama_lengkap, p.nip', false)
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'left')
|
|
->where('c.pegawai', $pegawaiId)
|
|
->where('c.status_cuti', 'Waiting');
|
|
$permohonanCuti = $permohonanCutiB->orderBy('c.tanggal_cuti', 'ASC')
|
|
->limit(5)
|
|
->get()
|
|
->getResultArray();
|
|
$permohonanCuti = array_map(
|
|
fn ($r): array => is_array($r) ? $this->normalizeCutiRowArrayForApi($r) : [],
|
|
$permohonanCuti,
|
|
);
|
|
|
|
$tpc = $this->db->table('cuti c')
|
|
->where('c.pegawai', $pegawaiId)
|
|
->where('c.status_cuti', 'Waiting');
|
|
$totalPermohonanCuti = (int) $tpc->countAllResults();
|
|
|
|
$totalPegawai = 1;
|
|
$persenLaki = $laki === 1 ? 100.0 : 0.0;
|
|
$persenWanita = $wanit === 1 ? 100.0 : 0.0;
|
|
if ($laki === 0 && $wanit === 0) {
|
|
$persenLaki = 0.0;
|
|
$persenWanita = 0.0;
|
|
}
|
|
|
|
$persenPresensi = $presensiHariIni >= 1 ? 100.0 : 0.0;
|
|
$persenCuti = $cutiHariIni >= 1 ? 100.0 : 0.0;
|
|
$persenBelumRekam = $belumRekam >= 1 ? 100.0 : 0.0;
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'tanggal_hari_ini' => $tanggalHariIni,
|
|
'total_pegawai' => $totalPegawai,
|
|
'pegawai_laki' => $laki,
|
|
'pegawai_perempuan' => $wanit,
|
|
'presensi_hari_ini' => $presensiHariIni,
|
|
'cuti_hari_ini' => $cutiHariIni,
|
|
'belum_rekam' => $belumRekam,
|
|
'persen_laki' => $persenLaki,
|
|
'persen_perempuan' => $persenWanita,
|
|
'persen_presensi' => $persenPresensi,
|
|
'persen_cuti' => $persenCuti,
|
|
'persen_belum_rekam' => $persenBelumRekam,
|
|
'permohonan_cuti' => $permohonanCuti,
|
|
'total_permohonan_cuti' => $totalPermohonanCuti,
|
|
'belum_rekam_pegawai' => [],
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function cutiList(string $status, int $page, int $perPage, ?int $cabangKantorId = null): array
|
|
{
|
|
$allowed = ['', 'Waiting', 'Approve', 'Rejected', 'Cancelled'];
|
|
if (! in_array($status, $allowed, true)) {
|
|
$status = 'Waiting';
|
|
}
|
|
|
|
$perPage = max(5, min(200, $perPage));
|
|
$page = max(1, $page);
|
|
$offset = ($page - 1) * $perPage;
|
|
|
|
$countB = $this->db->table('cuti c')->join('pegawai p', 'p.id_pegawai = c.pegawai', 'left');
|
|
$this->applyCabangPegawaiAlias($countB, 'p', $cabangKantorId);
|
|
if ($status !== '') {
|
|
$countB->where('c.status_cuti', $status);
|
|
}
|
|
$total = (int) $countB->countAllResults();
|
|
|
|
$b = $this->db->table('cuti c')
|
|
->select('c.id_cuti, c.pegawai, DATE_FORMAT(c.tanggal_cuti, \'%Y-%m-%d\') AS tanggal_cuti, c.tipe_cuti, c.alasan_cuti, c.status_cuti, c.alasan_tolak, p.nama_lengkap, p.nip', false)
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'left');
|
|
$this->applyCabangPegawaiAlias($b, 'p', $cabangKantorId);
|
|
if ($status !== '') {
|
|
$b->where('c.status_cuti', $status);
|
|
}
|
|
|
|
$rows = $b->orderBy('c.tanggal_cuti', 'DESC')
|
|
->orderBy('c.id_cuti', 'DESC')
|
|
->limit($perPage, $offset)
|
|
->get()
|
|
->getResultArray();
|
|
$rows = array_map(
|
|
fn ($r): array => is_array($r) ? $this->normalizeCutiRowArrayForApi($r) : [],
|
|
$rows,
|
|
);
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'rows' => $rows,
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $perPage,
|
|
'total_page' => (int) ceil($total / $perPage),
|
|
'status_filter'=> $status,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string, data?: mixed}
|
|
*/
|
|
public function cutiShow(int $id, ?int $cabangKantorId = null): array
|
|
{
|
|
$row = $this->db->table('cuti c')
|
|
->select('c.id_cuti, c.pegawai, DATE_FORMAT(c.tanggal_cuti, \'%Y-%m-%d\') AS tanggal_cuti, c.tipe_cuti, c.alasan_cuti, c.status_cuti, c.alasan_tolak, p.nama_lengkap, p.nip, p.email, p.kantor as pegawai_kantor', false)
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'left')
|
|
->where('c.id_cuti', $id)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if ($row === null) {
|
|
return ['status' => 0, 'pesan' => 'Data cuti tidak ditemukan'];
|
|
}
|
|
$row = $this->normalizeCutiRowArrayForApi($row);
|
|
if (! $this->pegawaiRowInCabang(['kantor' => $row['pegawai_kantor'] ?? null], $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data cuti tidak ditemukan'];
|
|
}
|
|
unset($row['pegawai_kantor']);
|
|
|
|
$dok = $this->db->table('cuti_dokumen')->select('dokumen')->where('cuti', $id)->get()->getResultArray();
|
|
|
|
return [
|
|
'status' => 1,
|
|
'pesan' => 'OK',
|
|
'data' => [
|
|
'cuti' => $row,
|
|
'dokumen' => $dok,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string}
|
|
*/
|
|
public function cutiApprove(int $id, ?int $cabangKantorId = null): array
|
|
{
|
|
$exists = $this->db->table('cuti')->select('id_cuti')->where('id_cuti', $id)->get()->getRowArray();
|
|
if ($exists === null) {
|
|
return ['status' => 0, 'pesan' => 'Data cuti tidak ditemukan'];
|
|
}
|
|
if (! $this->cutiAllowedForCabang($id, $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data cuti tidak ditemukan'];
|
|
}
|
|
|
|
$this->db->table('cuti')->where('id_cuti', $id)->update([
|
|
'status_cuti' => 'Approve',
|
|
'alasan_tolak' => '',
|
|
]);
|
|
|
|
return ['status' => 1, 'pesan' => 'Cuti disetujui.'];
|
|
}
|
|
|
|
/**
|
|
* @return array{status: int, pesan: string}
|
|
*/
|
|
public function cutiReject(int $id, string $alasanTolak, ?int $cabangKantorId = null): array
|
|
{
|
|
$exists = $this->db->table('cuti')->select('id_cuti')->where('id_cuti', $id)->get()->getRowArray();
|
|
if ($exists === null) {
|
|
return ['status' => 0, 'pesan' => 'Data cuti tidak ditemukan'];
|
|
}
|
|
if (! $this->cutiAllowedForCabang($id, $cabangKantorId)) {
|
|
return ['status' => 0, 'pesan' => 'Data cuti tidak ditemukan'];
|
|
}
|
|
|
|
$alasanTolak = trim($alasanTolak);
|
|
if ($alasanTolak === '') {
|
|
return ['status' => 0, 'pesan' => 'Alasan penolakan wajib diisi.'];
|
|
}
|
|
|
|
$this->db->table('cuti')->where('id_cuti', $id)->update([
|
|
'status_cuti' => 'Rejected',
|
|
'alasan_tolak' => $alasanTolak,
|
|
]);
|
|
|
|
return ['status' => 1, 'pesan' => 'Cuti ditolak.'];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
private function fetchPegawaiDetail(int $id): ?array
|
|
{
|
|
$row = $this->db->table('pegawai p')
|
|
->select('p.*, j.nama_jabatan, u.nama_unit_kerja, g.nama_golongan, k.nama_kantor, jd.nama_jadwal')
|
|
->join('jabatan j', 'j.id_jabatan = p.jabatan', 'left')
|
|
->join('unit_kerja u', 'u.id_unit_kerja = p.unit_kerja', 'left')
|
|
->join('golongan g', 'g.id_golongan = p.golongan_pekerjaan', 'left')
|
|
->join('kantor k', 'k.id_kantor = p.kantor', 'left')
|
|
->join('jadwal jd', 'jd.id_jadwal = p.jadwal', 'left')
|
|
->where('p.id_pegawai', $id)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if ($row !== null) {
|
|
unset($row['password'], $row['token']);
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
private function applyCabangPegawaiAlias(BaseBuilder $b, string $alias, ?int $cabangKantorId): void
|
|
{
|
|
if ($cabangKantorId !== null && $cabangKantorId > 0) {
|
|
$b->where("{$alias}.kantor", $cabangKantorId);
|
|
}
|
|
}
|
|
|
|
private function pegawaiIdAllowedForCabang(int $pegawaiId, ?int $cabangKantorId): bool
|
|
{
|
|
if ($cabangKantorId === null || $cabangKantorId <= 0) {
|
|
return true;
|
|
}
|
|
|
|
return $this->db->table('pegawai')->where('id_pegawai', $pegawaiId)->where('kantor', $cabangKantorId)->countAllResults() > 0;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row baris pegawai (key `kantor` = id_kantor)
|
|
*/
|
|
private function pegawaiRowInCabang(array $row, ?int $cabangKantorId): bool
|
|
{
|
|
if ($cabangKantorId === null || $cabangKantorId <= 0) {
|
|
return true;
|
|
}
|
|
|
|
return (int) ($row['kantor'] ?? 0) === $cabangKantorId;
|
|
}
|
|
|
|
private function cutiAllowedForCabang(int $cutiId, ?int $cabangKantorId): bool
|
|
{
|
|
if ($cabangKantorId === null || $cabangKantorId <= 0) {
|
|
return true;
|
|
}
|
|
$row = $this->db->table('cuti c')
|
|
->select('p.kantor')
|
|
->join('pegawai p', 'p.id_pegawai = c.pegawai', 'left')
|
|
->where('c.id_cuti', $cutiId)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
return $row !== null && (int) ($row['kantor'] ?? 0) === $cabangKantorId;
|
|
}
|
|
|
|
private function applyPegawaiSearchFilter(BaseBuilder $b, string $search): void
|
|
{
|
|
if ($search === '') {
|
|
return;
|
|
}
|
|
|
|
$b->groupStart()
|
|
->like('p.nama_lengkap', $search)
|
|
->orLike('p.nip', $search)
|
|
->orLike('p.username', $search)
|
|
->groupEnd();
|
|
}
|
|
|
|
private function normalizeDate(mixed $v): ?string
|
|
{
|
|
if ($v === null || $v === '' || $v === '0000-00-00') {
|
|
return null;
|
|
}
|
|
|
|
$s = (string) $v;
|
|
|
|
return $s;
|
|
}
|
|
|
|
/**
|
|
* Kunci baris cuti ke huruf kecil + rapikan bentuk tanggal_cuti setelah JSON (objek/array).
|
|
*
|
|
* @param array<string, mixed> $row
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function normalizeCutiRowArrayForApi(array $row): array
|
|
{
|
|
$row = array_change_key_case($row, CASE_LOWER);
|
|
$tc = $row['tanggal_cuti'] ?? null;
|
|
if (is_array($tc)) {
|
|
$row['tanggal_cuti'] = $tc['date'] ?? $tc['value'] ?? null;
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
}
|