Initial commit BIJ CI4
This commit is contained in:
977
app/Services/Admin/AdminApiService.php
Normal file
977
app/Services/Admin/AdminApiService.php
Normal file
@@ -0,0 +1,977 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
1106
app/Services/Admin/AdminExtraApiService.php
Normal file
1106
app/Services/Admin/AdminExtraApiService.php
Normal file
File diff suppressed because it is too large
Load Diff
263
app/Services/Admin/AdminUsersLoginService.php
Normal file
263
app/Services/Admin/AdminUsersLoginService.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Admin;
|
||||
|
||||
use App\Services\Mobile\MobileJsonService;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use Config\Database;
|
||||
|
||||
/**
|
||||
* Login admin memakai Ion Auth CI3: `admin_users` + relasi `admin_users_groups` / `admin_groups`.
|
||||
* Password: bcrypt ($2a$/$2y$) atau legacy MD5 (32 hex).
|
||||
* Setelah sukses, token API ditulis ke baris pegawai "proxy" (sama seperti sebelumnya).
|
||||
*/
|
||||
class AdminUsersLoginService
|
||||
{
|
||||
protected BaseConnection $db;
|
||||
|
||||
public function __construct(?BaseConnection $db = null)
|
||||
{
|
||||
$this->db = $db ?? Database::connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* ok: true,
|
||||
* token: string,
|
||||
* username: string,
|
||||
* admin_user_id: int,
|
||||
* group_names: list<string>,
|
||||
* group_ids: list<int>
|
||||
* }|array{ok: false, reason: 'skip'|'invalid'|'no_group'|'no_proxy'}
|
||||
*/
|
||||
/**
|
||||
* Cari id_pegawai dari identifier login (username pegawai atau NIP).
|
||||
*/
|
||||
public function resolvePegawaiIdFromCredentials(string $username): ?int
|
||||
{
|
||||
$username = trim($username);
|
||||
if ($username === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$p = $this->db->table('pegawai')->select('id_pegawai')->where('username', $username)->get()->getRowArray();
|
||||
if ($p !== null && ! empty($p['id_pegawai'])) {
|
||||
return (int) $p['id_pegawai'];
|
||||
}
|
||||
|
||||
$p = $this->db->table('pegawai')->select('id_pegawai')->where('nip', $username)->get()->getRowArray();
|
||||
if ($p !== null && ! empty($p['id_pegawai'])) {
|
||||
return (int) $p['id_pegawai'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pegawai yang di baris `admin_users` punya `id_pegawai` sama + aktif + punya grup Ion
|
||||
* → panel memakai RBAC grup (supervisor, HRD, dll.), bukan mode pegawai panel tipis.
|
||||
*
|
||||
* @return array{admin_user_id: int, username: string, group_names: list<string>}|null
|
||||
*/
|
||||
public function findLinkedAdminForPegawaiId(int $pegawaiId): ?array
|
||||
{
|
||||
if ($pegawaiId <= 0 || ! $this->db->tableExists('admin_users')) {
|
||||
return null;
|
||||
}
|
||||
if (! $this->db->fieldExists('id_pegawai', 'admin_users')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $this->db->table('admin_users')
|
||||
->where('id_pegawai', $pegawaiId)
|
||||
->where('active', 1)
|
||||
->orderBy('id', 'ASC')
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($row === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$userId = (int) ($row['id'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$groups = $this->loadAdminGroupsForUser($userId);
|
||||
if ($this->db->tableExists('admin_users_groups') && $groups['names'] === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'admin_user_id' => $userId,
|
||||
'username' => (string) ($row['username'] ?? ''),
|
||||
'group_names' => $groups['names'],
|
||||
];
|
||||
}
|
||||
|
||||
public function tryLogin(string $username, string $password): array
|
||||
{
|
||||
if ($username === '' || $password === '') {
|
||||
return ['ok' => false, 'reason' => 'skip'];
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('admin_users')) {
|
||||
return ['ok' => false, 'reason' => 'skip'];
|
||||
}
|
||||
|
||||
$row = $this->db->table('admin_users')
|
||||
->where('username', $username)
|
||||
->where('active', 1)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($row === null || empty($row['password'])) {
|
||||
return ['ok' => false, 'reason' => 'invalid'];
|
||||
}
|
||||
|
||||
$hash = (string) $row['password'];
|
||||
if (! $this->verifyAdminPassword($password, $hash)) {
|
||||
return ['ok' => false, 'reason' => 'invalid'];
|
||||
}
|
||||
|
||||
$userId = (int) $row['id'];
|
||||
$groups = $this->loadAdminGroupsForUser($userId);
|
||||
|
||||
if ($this->db->tableExists('admin_users_groups') && $groups === []) {
|
||||
return ['ok' => false, 'reason' => 'no_group'];
|
||||
}
|
||||
|
||||
$proxyId = $this->resolveProxyPegawaiId($groups, $row);
|
||||
if ($proxyId === null) {
|
||||
return ['ok' => false, 'reason' => 'no_proxy'];
|
||||
}
|
||||
|
||||
$mobile = new MobileJsonService($this->db);
|
||||
$token = $mobile->issueTokenForPegawaiId($proxyId);
|
||||
if ($token === null) {
|
||||
return ['ok' => false, 'reason' => 'no_proxy'];
|
||||
}
|
||||
|
||||
$this->db->table('admin_users')->where('id', $userId)->update([
|
||||
'last_login' => time(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'token' => $token,
|
||||
'username' => $username,
|
||||
'admin_user_id' => $userId,
|
||||
'group_names' => $groups['names'],
|
||||
'group_ids' => $groups['ids'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{names: list<string>, ids: list<int>}
|
||||
*/
|
||||
private function loadAdminGroupsForUser(int $userId): array
|
||||
{
|
||||
if (! $this->db->tableExists('admin_users_groups') || ! $this->db->tableExists('admin_groups')) {
|
||||
return ['names' => [], 'ids' => []];
|
||||
}
|
||||
|
||||
$rows = $this->db->table('admin_users_groups ug')
|
||||
->select('g.id, g.name')
|
||||
->join('admin_groups g', 'g.id = ug.group_id', 'inner')
|
||||
->where('ug.user_id', $userId)
|
||||
->orderBy('g.id', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$names = [];
|
||||
$ids = [];
|
||||
foreach ($rows as $r) {
|
||||
$ids[] = (int) ($r['id'] ?? 0);
|
||||
$names[] = (string) ($r['name'] ?? '');
|
||||
}
|
||||
|
||||
return ['names' => $names, 'ids' => $ids];
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy pegawai untuk token API. Bisa diarahkan per grup (mis. HRD) lewat .env.
|
||||
*
|
||||
* @param array{names: list<string>, ids: list<int>} $groups
|
||||
* @param array<string, mixed>|null $adminRow Baris admin_users (untuk id_pegawai per-akun).
|
||||
*/
|
||||
private function resolveProxyPegawaiId(array $groups, ?array $adminRow = null): ?int
|
||||
{
|
||||
if ($adminRow !== null && $this->db->fieldExists('id_pegawai', 'admin_users')) {
|
||||
$assigned = (int) ($adminRow['id_pegawai'] ?? 0);
|
||||
if ($assigned > 0 && $this->pegawaiExists($assigned)) {
|
||||
return $assigned;
|
||||
}
|
||||
}
|
||||
|
||||
$fromEnv = env('ADMIN_LOGIN_PROXY_PEGAWAI_ID');
|
||||
if (is_string($fromEnv) && $fromEnv !== '') {
|
||||
$id = (int) $fromEnv;
|
||||
if ($id > 0 && $this->pegawaiExists($id)) {
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
$hrdOnly = in_array('hrd', array_map('strtolower', $groups['names']), true)
|
||||
&& ! in_array('webmaster', array_map('strtolower', $groups['names']), true);
|
||||
if ($hrdOnly) {
|
||||
$hrdEnv = env('ADMIN_LOGIN_PROXY_PEGAWAI_ID_HRD');
|
||||
if (is_string($hrdEnv) && $hrdEnv !== '') {
|
||||
$hid = (int) $hrdEnv;
|
||||
if ($hid > 0 && $this->pegawaiExists($hid)) {
|
||||
return $hid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$super = $this->db->table('pegawai')
|
||||
->select('id_pegawai')
|
||||
->where('super_akses', 'true')
|
||||
->orderBy('id_pegawai', 'ASC')
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($super !== null && ! empty($super['id_pegawai'])) {
|
||||
return (int) $super['id_pegawai'];
|
||||
}
|
||||
|
||||
$any = $this->db->table('pegawai')
|
||||
->select('id_pegawai')
|
||||
->orderBy('id_pegawai', 'ASC')
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if ($any !== null && ! empty($any['id_pegawai'])) {
|
||||
return (int) $any['id_pegawai'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function verifyAdminPassword(string $plain, string $hash): bool
|
||||
{
|
||||
if (str_starts_with($hash, '$2y$') || str_starts_with($hash, '$2a$') || str_starts_with($hash, '$2b$')) {
|
||||
return password_verify($plain, $hash);
|
||||
}
|
||||
|
||||
if (strlen($hash) === 32 && ctype_xdigit($hash)) {
|
||||
return md5($plain) === $hash;
|
||||
}
|
||||
|
||||
return password_verify($plain, $hash);
|
||||
}
|
||||
|
||||
private function pegawaiExists(int $id): bool
|
||||
{
|
||||
return $this->db->table('pegawai')->where('id_pegawai', $id)->countAllResults() > 0;
|
||||
}
|
||||
}
|
||||
194
app/Services/AdminAuditService.php
Normal file
194
app/Services/AdminAuditService.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use Config\Database;
|
||||
use Config\Services;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Audit trail untuk aksi admin (API `/api/admin/*` dan keamanan).
|
||||
*/
|
||||
class AdminAuditService
|
||||
{
|
||||
private const MAX_PAYLOAD_CHARS = 65535;
|
||||
|
||||
private BaseConnection $db;
|
||||
|
||||
public function __construct(?BaseConnection $db = null)
|
||||
{
|
||||
$this->db = $db ?? Database::connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Catat satu baris audit. `payload` disimpan sebagai JSON (disederhanakan & dibatasi panjang).
|
||||
* Konteks sesi (auth_source, grup Ion) diambil dari session saat dipanggil dari request web.
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
public function log(string $action, array $payload = []): void
|
||||
{
|
||||
try {
|
||||
if (! $this->db->tableExists('admin_activity_logs')) {
|
||||
log_message('debug', 'AdminAuditService: tabel admin_activity_logs belum ada — jalankan migrasi.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$req = Services::request();
|
||||
$sess = session();
|
||||
|
||||
$outcome = (string) ($payload['__outcome'] ?? 'success');
|
||||
$body = $payload;
|
||||
unset($body['__outcome']);
|
||||
|
||||
$row = [
|
||||
'admin_user' => $this->resolveAdminUserLabel($sess, $body),
|
||||
'action' => $this->truncate($action, 128),
|
||||
'endpoint' => $this->truncate($req->getUri()->getPath(), 512),
|
||||
'payload' => $this->encodePayload($this->enrichPayload($req, $sess, $body)),
|
||||
'ip_address' => $this->truncate($req->getIPAddress(), 45),
|
||||
'user_agent' => $this->truncate($req->getUserAgent()->__toString(), 512),
|
||||
'auth_source' => $this->truncate((string) ($sess->get('admin_auth_source') ?? ''), 32) ?: null,
|
||||
'roles_json' => $this->encodeRolesJson($sess),
|
||||
'outcome' => $this->truncate($outcome, 32),
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
$this->db->table('admin_activity_logs')->insert($row);
|
||||
} catch (Throwable $e) {
|
||||
log_message('error', 'AdminAuditService::log gagal: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $sessPayload
|
||||
*/
|
||||
private function resolveAdminUserLabel($sess, array $sessPayload): string
|
||||
{
|
||||
$u = $sess->get('admin_username');
|
||||
if (is_string($u) && $u !== '') {
|
||||
return $this->truncate($u, 191);
|
||||
}
|
||||
|
||||
$actor = $sessPayload['actor'] ?? null;
|
||||
if (is_array($actor)) {
|
||||
$nip = (string) ($actor['nip'] ?? '');
|
||||
$id = (string) ($actor['id_pegawai'] ?? '');
|
||||
if ($nip !== '') {
|
||||
return $this->truncate('pegawai:' . $nip, 191);
|
||||
}
|
||||
if ($id !== '') {
|
||||
return $this->truncate('pegawai:id:' . $id, 191);
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function enrichPayload(RequestInterface $req, $sess, array $payload): array
|
||||
{
|
||||
$out = $this->sanitizeForStorage($payload);
|
||||
|
||||
$out['_http'] = [
|
||||
'method' => $req->getMethod(),
|
||||
'uri' => (string) $req->getUri(),
|
||||
];
|
||||
|
||||
$out['_session_panel'] = [
|
||||
'has_admin_token' => is_string($sess->get('admin_mobile_token')) && $sess->get('admin_mobile_token') !== '',
|
||||
'auth_source' => $sess->get('admin_auth_source'),
|
||||
'ion_groups' => $sess->get('admin_ion_groups'),
|
||||
];
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sanitizeForStorage(array $data): array
|
||||
{
|
||||
$redactKeys = ['password', 'token', 'admin_mobile_token'];
|
||||
|
||||
return $this->stripSensitiveRecursive($data, $redactKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @param list<string> $redactKeys
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function stripSensitiveRecursive(array $data, array $redactKeys): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($data as $k => $v) {
|
||||
$key = (string) $k;
|
||||
if (in_array(strtolower($key), $redactKeys, true)) {
|
||||
$out[$key] = '[redacted]';
|
||||
|
||||
continue;
|
||||
}
|
||||
if (is_array($v)) {
|
||||
$out[$key] = $this->stripSensitiveRecursive($v, $redactKeys);
|
||||
} elseif (is_scalar($v) || $v === null) {
|
||||
$out[$key] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
private function encodePayload(array $payload): ?string
|
||||
{
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
|
||||
if ($json === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strlen($json) > self::MAX_PAYLOAD_CHARS) {
|
||||
$json = substr($json, 0, self::MAX_PAYLOAD_CHARS - 40) . '…[truncated]';
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
private function encodeRolesJson($sess): ?string
|
||||
{
|
||||
$g = $sess->get('admin_ion_groups');
|
||||
if (! is_array($g)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$enc = json_encode(array_values($g), JSON_UNESCAPED_UNICODE);
|
||||
if ($enc === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return strlen($enc) > 16000 ? substr($enc, 0, 16000) : $enc;
|
||||
}
|
||||
|
||||
private function truncate(string $s, int $max): string
|
||||
{
|
||||
if (strlen($s) <= $max) {
|
||||
return $s;
|
||||
}
|
||||
|
||||
return substr($s, 0, max(0, $max - 3)) . '...';
|
||||
}
|
||||
}
|
||||
219
app/Services/ApiClient.php
Normal file
219
app/Services/ApiClient.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use CodeIgniter\HTTP\CURLRequest;
|
||||
use Config\Services;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Klien HTTP ke API internal (`/api/mobile/*` dan `/api/admin/*`).
|
||||
* Controller admin web tidak mengakses DB; hanya memanggil API lewat kelas ini.
|
||||
*/
|
||||
class ApiClient
|
||||
{
|
||||
private CURLRequest $client;
|
||||
|
||||
public function __construct(?CURLRequest $client = null)
|
||||
{
|
||||
$this->client = $client ?? Services::curlrequest([
|
||||
'timeout' => 30,
|
||||
'http_errors' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, scalar|null> $formData
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
public function postMobile(string $method, array $formData = []): array
|
||||
{
|
||||
helper(['url']);
|
||||
|
||||
return $this->post('api/mobile/' . ltrim($method, '/'), $formData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, scalar|null> $formData
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
public function postMobileWithToken(string $method, string $token, array $formData = []): array
|
||||
{
|
||||
$formData['token'] = $token;
|
||||
|
||||
return $this->postMobile($method, $formData);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET ke `/api/admin/{path}` — token ditambahkan ke query string.
|
||||
*
|
||||
* @param array<string, scalar|null> $query
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
public function getAdmin(string $path, ?string $token, array $query = []): array
|
||||
{
|
||||
helper(['url']);
|
||||
if ($token !== null && $token !== '') {
|
||||
$query['token'] = $token;
|
||||
}
|
||||
|
||||
$forward = $this->adminInternalHeaders();
|
||||
if (($forward['Cookie'] ?? '') !== '' && session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
return $this->get('api/admin/' . ltrim($path, '/'), $query, $forward);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST form ke `/api/admin/{path}`.
|
||||
*
|
||||
* @param array<string, scalar|null> $formData
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
public function postAdmin(string $path, ?string $token, array $formData = []): array
|
||||
{
|
||||
helper(['url']);
|
||||
if ($token !== null && $token !== '') {
|
||||
$formData['token'] = $token;
|
||||
}
|
||||
|
||||
$forward = $this->adminInternalHeaders();
|
||||
if (($forward['Cookie'] ?? '') !== '' && session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
return $this->post('api/admin/' . ltrim($path, '/'), $formData, $forward);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET generik (relatif terhadap root site, mis. `api/admin/pegawai`).
|
||||
*
|
||||
* @param array<string, scalar|null> $query
|
||||
* @param array<string, string> $extraHeaders
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
public function get(string $path, array $query = [], array $extraHeaders = []): array
|
||||
{
|
||||
helper(['url']);
|
||||
$url = base_url(ltrim($path, '/'));
|
||||
|
||||
$base = $this->emptyResult();
|
||||
|
||||
$headers = array_merge(
|
||||
['Accept' => 'application/json'],
|
||||
$extraHeaders
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->client->get($url, [
|
||||
'query' => $query,
|
||||
'headers' => $headers,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
$base['error'] = $e->getMessage();
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
return $this->fillFromResponse($base, $response->getStatusCode(), $response->getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* POST `application/x-www-form-urlencoded`.
|
||||
*
|
||||
* @param array<string, scalar|null> $formData
|
||||
* @param array<string, string> $extraHeaders
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
public function post(string $path, array $formData = [], array $extraHeaders = []): array
|
||||
{
|
||||
helper(['url']);
|
||||
$url = base_url(ltrim($path, '/'));
|
||||
|
||||
$base = $this->emptyResult();
|
||||
|
||||
$headers = array_merge(
|
||||
['Accept' => 'application/json'],
|
||||
$extraHeaders
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $this->client->post($url, [
|
||||
'form_params' => $formData,
|
||||
'headers' => $headers,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
$base['error'] = $e->getMessage();
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
return $this->fillFromResponse($base, $response->getStatusCode(), $response->getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Teruskan cookie browser ke hit internal `/api/admin/*` agar sesi panel sama.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function adminInternalHeaders(): array
|
||||
{
|
||||
$cookie = Services::request()->getHeaderLine('Cookie');
|
||||
|
||||
return $cookie !== '' ? ['Cookie' => $cookie] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $json
|
||||
*/
|
||||
public static function isSuccess(?array $json): bool
|
||||
{
|
||||
return is_array($json) && (int) ($json['status'] ?? 0) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
private function emptyResult(): array
|
||||
{
|
||||
return [
|
||||
'transport_ok' => false,
|
||||
'http_code' => 0,
|
||||
'json' => null,
|
||||
'error' => null,
|
||||
'raw' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string} $base
|
||||
*
|
||||
* @return array{transport_ok: bool, http_code: int, json: array<string, mixed>|null, error: string|null, raw: string}
|
||||
*/
|
||||
private function fillFromResponse(array $base, int $statusCode, string $body): array
|
||||
{
|
||||
$base['http_code'] = $statusCode;
|
||||
$base['raw'] = $body;
|
||||
$base['transport_ok'] = $statusCode === 200;
|
||||
|
||||
$decoded = json_decode($body, true);
|
||||
if (! is_array($decoded)) {
|
||||
$base['error'] = 'Respons bukan JSON objek';
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
$base['json'] = $decoded;
|
||||
|
||||
return $base;
|
||||
}
|
||||
}
|
||||
786
app/Services/Mobile/MobileJsonService.php
Normal file
786
app/Services/Mobile/MobileJsonService.php
Normal file
@@ -0,0 +1,786 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Mobile;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use Config\Database;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Port logika bisnis dari CI3 `application/controllers/Json.php`.
|
||||
* Respons struktur disamakan dengan legacy untuk kompatibilitas aplikasi mobile.
|
||||
*
|
||||
* TODO (fase berikutnya): pecah per agregat (PresensiService, CutiService), tambahkan transaksi,
|
||||
* perbaiki keamanan (MD5 → password_hash, token → JWT) setelah cutover klien.
|
||||
*/
|
||||
class MobileJsonService
|
||||
{
|
||||
protected BaseConnection $db;
|
||||
|
||||
/** @var array<int, string> */
|
||||
private array $hariIndo = [
|
||||
1 => 'Senin',
|
||||
2 => 'Selasa',
|
||||
3 => 'Rabu',
|
||||
4 => 'Kamis',
|
||||
5 => 'Jumat',
|
||||
6 => 'Sabtu',
|
||||
7 => 'Minggu',
|
||||
];
|
||||
|
||||
public function __construct(?BaseConnection $db = null)
|
||||
{
|
||||
$this->db = $db ?? Database::connect();
|
||||
}
|
||||
|
||||
public function loginWToken(string $token): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->get()->getRow();
|
||||
if ($pegawai) {
|
||||
unset($pegawai->username, $pegawai->password, $pegawai->token);
|
||||
$this->db->table('pegawai')->where('id_pegawai', $pegawai->id_pegawai)->update([
|
||||
'last_login' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$data['status'] = 1;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function login(string $user, string $pass): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => 'Username atau Password tidak sesuai',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')
|
||||
->where('username', $user)
|
||||
->where('password', md5($pass))
|
||||
->get()
|
||||
->getRow();
|
||||
|
||||
if ($pegawai) {
|
||||
unset($pegawai->username, $pegawai->password, $pegawai->token);
|
||||
$token = md5((string) $pegawai->id_pegawai) . $this->generateRandomString(15);
|
||||
$this->db->table('pegawai')->where('id_pegawai', $pegawai->id_pegawai)->update([
|
||||
'token' => $token,
|
||||
'last_login' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'Selamat datang';
|
||||
$data['token'] = $token;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terbitkan token API mobile untuk pegawai (id_pegawai), seperti setelah login sukses.
|
||||
* Dipakai saat login admin lewat tabel admin_users (proxy token ke pegawai tertentu).
|
||||
*/
|
||||
public function issueTokenForPegawaiId(int $id): ?string
|
||||
{
|
||||
$pegawai = $this->db->table('pegawai')->where('id_pegawai', $id)->get()->getRow();
|
||||
if ($pegawai === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$token = md5((string) $pegawai->id_pegawai) . $this->generateRandomString(15);
|
||||
$this->db->table('pegawai')->where('id_pegawai', $id)->update([
|
||||
'token' => $token,
|
||||
'last_login' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function profil(string $token): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')
|
||||
->select('*, COALESCE(tanggal_lahir, \'0000-00-00\') as tanggal_lahir', false)
|
||||
->where('token', $token)
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRow();
|
||||
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
unset($pegawai->username, $pegawai->password, $pegawai->token);
|
||||
|
||||
$pegawai->kantor = $this->db->table('kantor')->where('id_kantor', $pegawai->kantor)->limit(1)->get()->getRow();
|
||||
$pegawai->jabatan = $this->db->table('jabatan')->where('id_jabatan', $pegawai->jabatan)->limit(1)->get()->getRow();
|
||||
$pegawai->unit_kerja = $this->db->table('unit_kerja')->where('id_unit_kerja', $pegawai->unit_kerja)->limit(1)->get()->getRow();
|
||||
$pegawai->lembur = $this->db->table('lembur')->where('pegawai', $pegawai->id_pegawai)->where('tanggal_lembur', date('Y-m-d'))->limit(1)->get()->getRow();
|
||||
|
||||
$dilapangan = $this->db->table('dilapangan')->where('pegawai', $pegawai->id_pegawai)->limit(1)->get()->getRow();
|
||||
$pegawai->dilapangan = $dilapangan ? true : false;
|
||||
|
||||
$hari = (int) date('N');
|
||||
$pre_in = $hari . '_in';
|
||||
$pre_out = $hari . '_out';
|
||||
|
||||
$jadwal_ar = [
|
||||
'hari' => $this->hariIndo[$hari],
|
||||
'masuk' => '',
|
||||
'pulang' => '',
|
||||
'istirahat' => '',
|
||||
'toleransi_masuk' => '0',
|
||||
'toleransi_pulang' => '0',
|
||||
'libur' => true,
|
||||
];
|
||||
|
||||
$libur_perusahaan = $this->db->table('libur')->where('tanggal_libur', date('Y-m-d'))->limit(1)->get()->getRow();
|
||||
if ($libur_perusahaan) {
|
||||
$jadwal_ar['ket_libur'] = 'Libur: ' . $libur_perusahaan->keterangan_libur;
|
||||
} else {
|
||||
$cuti = $this->db->table('cuti')
|
||||
->where('pegawai', $pegawai->id_pegawai)
|
||||
->where('tanggal_cuti', date('Y-m-d'))
|
||||
->where('status_cuti', 'Approve')
|
||||
->limit(1)
|
||||
->get()
|
||||
->getRow();
|
||||
if ($cuti) {
|
||||
$jadwal_ar['ket_libur'] = 'Cuti: ' . $cuti->alasan_cuti;
|
||||
} else {
|
||||
$jadwal = $this->db->table('jadwal')->where('id_jadwal', $pegawai->jadwal)->limit(1)->get()->getRow();
|
||||
if ($jadwal) {
|
||||
$jadwal_ar['masuk'] = $jadwal->{$pre_in};
|
||||
$jadwal_ar['pulang'] = $jadwal->{$pre_out};
|
||||
$jadwal_ar['istirahat'] = '12:00';
|
||||
$jadwal_ar['toleransi_masuk'] = $jadwal->toleransi_terlambat;
|
||||
$jadwal_ar['toleransi_pulang'] = $jadwal->toleransi_pulang_cepat;
|
||||
$jadwal_ar['libur'] = false;
|
||||
$jadwal_ar['ket_libur'] = '';
|
||||
} else {
|
||||
$jadwal_ar['ket_libur'] = 'Tidak ada jadwal. Hubungi Petugas';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pegawai->jadwal = $jadwal_ar;
|
||||
$data['status'] = 1;
|
||||
$data['pegawai'] = $pegawai;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function saveCuti(string $token, string $nama_photo, string $img, string $tanggal, string $alasan, string $tipe): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$dir = $this->ensureUploadDir('dokcuti');
|
||||
$image = base64_decode($img);
|
||||
$image_name = uniqid((string) mt_rand(), true);
|
||||
$filename = $image_name . '-' . $nama_photo;
|
||||
|
||||
if ($image === false || $image === '' || file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $image) === false) {
|
||||
$data['pesan'] = 'Photo Dokumen GAGAL upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->db->table('cuti')->insert([
|
||||
'pegawai' => $pegawai->id_pegawai,
|
||||
'tanggal_cuti' => $tanggal,
|
||||
'tipe_cuti' => $tipe,
|
||||
'alasan_cuti' => $alasan,
|
||||
'status_cuti' => 'Waiting',
|
||||
'alasan_tolak' => '',
|
||||
]);
|
||||
|
||||
$id = (int) $this->db->insertID();
|
||||
$this->db->table('cuti_dokumen')->insert([
|
||||
'cuti' => $id,
|
||||
'dokumen' => $filename,
|
||||
]);
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'Photo Dokumen berhasil di upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string|null $id
|
||||
*/
|
||||
public function batalkanCuti(string $token, $id): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->db->table('cuti')->where('pegawai', $pegawai->id_pegawai)->where('id_cuti', $id)->update([
|
||||
'status_cuti' => 'Cancelled',
|
||||
]);
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = "Ajuan di batalkan {$id} - {$pegawai->id_pegawai}";
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function saveAktifitas(string $token, string $nama_photo, string $img, string $tanggal, string $deksripsi): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$dir = $this->ensureUploadDir('aktifitas');
|
||||
$image = base64_decode($img);
|
||||
$image_name = uniqid((string) mt_rand(), true);
|
||||
$filename = $image_name . '-' . $nama_photo;
|
||||
|
||||
if ($image === false || $image === '' || file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $image) === false) {
|
||||
$data['pesan'] = 'Photo Kegiatan GAGAL upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->db->table('aktifitas_harian')->insert([
|
||||
'pegawai' => $pegawai->id_pegawai,
|
||||
'image' => $filename,
|
||||
'deskripsi' => $deksripsi,
|
||||
'waktu_aktifitas' => $tanggal,
|
||||
]);
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'Photo Kegiatan berhasil di upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function saveMasuk(string $token, string $nama_photo, string $img, string $lat, string $lng, string $jarak): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => 'Tidak ada jadwal kerja',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$jadwal = $this->db->table('jadwal')->where('id_jadwal', $pegawai->jadwal)->limit(1)->get()->getRow();
|
||||
if (! $jadwal) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$dir = $this->ensureUploadDir('absen' . DIRECTORY_SEPARATOR . 'masuk');
|
||||
$image = base64_decode($img);
|
||||
$image_name = uniqid((string) mt_rand(), true);
|
||||
$filename = $image_name . '-' . $nama_photo;
|
||||
|
||||
if ($image === false || $image === '' || file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $image) === false) {
|
||||
$data['pesan'] = 'Photo Kehadiran GAGAL upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
$hari = (int) date('N');
|
||||
$pre_in = $hari . '_in';
|
||||
|
||||
$masuk_jadwal = date('Y-m-d') . ' ' . $jadwal->{$pre_in};
|
||||
$jam_masuk = strtotime($masuk_jadwal);
|
||||
$jam_toleransi = strtotime($masuk_jadwal) + ((int) $jadwal->toleransi_terlambat * 60);
|
||||
$jam_sekarang = time();
|
||||
|
||||
if ($jam_sekarang < $jam_masuk) {
|
||||
$ket_masuk = 'Sesuai jadwal';
|
||||
} elseif ($jam_sekarang >= $jam_masuk && $jam_sekarang <= $jam_toleransi) {
|
||||
$ket_masuk = 'Terlambat';
|
||||
} else {
|
||||
$ket_masuk = 'Sangat terlambat';
|
||||
}
|
||||
|
||||
$this->db->table('presensi')->where('tanggal', date('Y-m-d'))->where('pegawai', $pegawai->id_pegawai)->update([
|
||||
'jam_masuk' => date('Y-m-d H:i:s'),
|
||||
'ket_masuk' => $ket_masuk,
|
||||
'photo_masuk' => $filename,
|
||||
'lat_masuk' => $lat,
|
||||
'lng_masuk' => $lng,
|
||||
'jarak_masuk' => $jarak,
|
||||
]);
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'Photo Kehadiran berhasil di upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function savePulang(string $token, string $nama_photo, string $img, string $lat, string $lng, string $jarak): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => 'Tidak ada jadwal kerja',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$jadwal = $this->db->table('jadwal')->where('id_jadwal', $pegawai->jadwal)->limit(1)->get()->getRow();
|
||||
if (! $jadwal) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$dir = $this->ensureUploadDir('absen' . DIRECTORY_SEPARATOR . 'pulang');
|
||||
$image = base64_decode($img);
|
||||
$image_name = uniqid((string) mt_rand(), true);
|
||||
$filename = $image_name . '-' . $nama_photo;
|
||||
|
||||
if ($image === false || $image === '' || file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $image) === false) {
|
||||
$data['pesan'] = 'Photo Kehadiran GAGAL upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
$wibNow = new \DateTimeImmutable('now', new \DateTimeZone('Asia/Jakarta'));
|
||||
$hari = (int) $wibNow->format('N');
|
||||
$pre_out = $hari . '_out';
|
||||
|
||||
$jadwal_pulang = $wibNow->format('Y-m-d') . ' ' . $jadwal->{$pre_out};
|
||||
$jam_pulang = strtotime($jadwal_pulang);
|
||||
$jam_toleransi = strtotime($jadwal_pulang) - ((int) $jadwal->toleransi_terlambat * 60);
|
||||
$jam_sekarang = $wibNow->getTimestamp();
|
||||
|
||||
if ($jam_sekarang < $jam_toleransi) {
|
||||
$data['pesan'] = 'Belum waktunya Pulang, waktu pulang anda adalah pukul ' . $jadwal->{$pre_out} . '. Jika anda memerlukan untuk pulang cepat, silahkan hubungi Operator';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ($jam_sekarang >= $jam_toleransi && $jam_sekarang <= $jam_pulang) {
|
||||
$ket_pulang = 'Pulang Cepat';
|
||||
} else {
|
||||
$ket_pulang = 'Sesuai jadwal';
|
||||
}
|
||||
|
||||
$this->db->table('presensi')->where('tanggal', $wibNow->format('Y-m-d'))->where('pegawai', $pegawai->id_pegawai)->update([
|
||||
'jam_pulang' => $wibNow->format('Y-m-d H:i:s'),
|
||||
'ket_pulang' => $ket_pulang,
|
||||
'photo_pulang' => $filename,
|
||||
'lat_pulang' => $lat,
|
||||
'lng_pulang' => $lng,
|
||||
'jarak_pulang' => $jarak,
|
||||
]);
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'Photo Kehadiran berhasil di upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function saveIstirahat(string $token, string $mulai, string $selesai): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => 'Tidak ada jadwal kerja',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$jadwal = $this->db->table('jadwal')->where('id_jadwal', $pegawai->jadwal)->limit(1)->get()->getRow();
|
||||
if (! $jadwal) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$ist = null;
|
||||
if ($mulai !== '') {
|
||||
$ist = [
|
||||
'mulai_istirahat' => $mulai,
|
||||
'beres_istirahat' => $mulai,
|
||||
];
|
||||
}
|
||||
|
||||
if ($selesai !== '') {
|
||||
$ist = [
|
||||
'beres_istirahat' => $selesai,
|
||||
'is_istirahat' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
if ($ist === null) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->db->table('presensi')->where('tanggal', date('Y-m-d'))->where('pegawai', $pegawai->id_pegawai)->update($ist);
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'berhasil disimpan';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function presensiToday(string $token): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai, jadwal')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$presensi = $this->db->table('presensi')->where('tanggal', date('Y-m-d'))->where('pegawai', $pegawai->id_pegawai)->limit(1)->get()->getRow();
|
||||
|
||||
if (! $presensi) {
|
||||
$jadwal = $this->db->table('jadwal')->where('id_jadwal', $pegawai->jadwal)->get()->getRow();
|
||||
$ins = [
|
||||
'pegawai' => $pegawai->id_pegawai,
|
||||
'tanggal' => date('Y-m-d'),
|
||||
'jadwal' => serialize($jadwal),
|
||||
'jam_masuk' => null,
|
||||
'ket_masuk' => '',
|
||||
'photo_masuk' => '',
|
||||
'jam_pulang' => null,
|
||||
'ket_pulang' => '',
|
||||
'photo_pulang' => '',
|
||||
'mulai_istirahat' => null,
|
||||
'beres_istirahat' => null,
|
||||
'is_istirahat' => '0',
|
||||
'lat_masuk' => '0',
|
||||
'lng_masuk' => '0',
|
||||
'lat_pulang' => '0',
|
||||
'lng_pulang' => '0',
|
||||
'jarak_masuk' => '0',
|
||||
'jarak_pulang' => '0',
|
||||
];
|
||||
$this->db->table('presensi')->insert($ins);
|
||||
$id = (int) $this->db->insertID();
|
||||
|
||||
$ins['id_presensi'] = $id . '.';
|
||||
|
||||
$presensi = (object) $ins;
|
||||
}
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $presensi;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function presensi(string $token): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai, jadwal')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$presensi = $this->db->table('presensi')->where('pegawai', $pegawai->id_pegawai)->orderBy('tanggal', 'DESC')->limit(20)->get()->getResult();
|
||||
if ($presensi) {
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $presensi;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function daftarToday(string $token): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai, jadwal')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$today = date('Y-m-d');
|
||||
$presensi = $this->db->table('pegawai')
|
||||
->join('jabatan', 'id_jabatan=jabatan', 'left')
|
||||
->join('presensi', 'id_pegawai=pegawai AND tanggal = ' . $this->db->escape($today), 'left', false)
|
||||
->where('id_pegawai !=', $pegawai->id_pegawai)
|
||||
->where('id_presensi IS NOT NULL', null, false)
|
||||
->orderBy('jam_masuk', 'DESC')
|
||||
->orderBy('nama_lengkap')
|
||||
->get()
|
||||
->getResult();
|
||||
|
||||
if ($presensi) {
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $presensi;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function berita(string $token, $dari, $jumlah): array
|
||||
{
|
||||
if ($dari === '' || $dari === null) {
|
||||
$dari = 0;
|
||||
}
|
||||
if ($jumlah === '' || $jumlah === null) {
|
||||
$jumlah = 10;
|
||||
}
|
||||
$dari = (int) $dari;
|
||||
$jumlah = (int) $jumlah;
|
||||
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$berita = $this->db->table('berita')->orderBy('tanggal', 'DESC')->limit($jumlah, $dari)->get()->getResult();
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $berita;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function cuti(string $token, $dari, $jumlah): array
|
||||
{
|
||||
if ($dari === '' || $dari === null) {
|
||||
$dari = 0;
|
||||
}
|
||||
if ($jumlah === '' || $jumlah === null) {
|
||||
$jumlah = 25;
|
||||
}
|
||||
$dari = (int) $dari;
|
||||
$jumlah = (int) $jumlah;
|
||||
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$cuti = $this->db->table('cuti')->where('pegawai', $pegawai->id_pegawai)->orderBy('tanggal_cuti', 'DESC')->limit($jumlah, $dari)->get()->getResult();
|
||||
|
||||
if ($cuti) {
|
||||
foreach ($cuti as $v) {
|
||||
$v->dokumen = $this->db->table('cuti_dokumen')->select('dokumen')->where('cuti', $v->id_cuti)->get()->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $cuti;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function lembur(string $token, $dari, $jumlah): array
|
||||
{
|
||||
if ($dari === '' || $dari === null) {
|
||||
$dari = 0;
|
||||
}
|
||||
if ($jumlah === '' || $jumlah === null) {
|
||||
$jumlah = 25;
|
||||
}
|
||||
$dari = (int) $dari;
|
||||
$jumlah = (int) $jumlah;
|
||||
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$rows = $this->db->table('lembur')->where('pegawai', $pegawai->id_pegawai)->orderBy('tanggal_lembur', 'DESC')->limit($jumlah, $dari)->get()->getResult();
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $rows;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function libur(string $token): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$rows = $this->db->table('libur')->orderBy('tanggal_libur', 'DESC')->get()->getResult();
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $rows;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function aktifitas(string $token, $dari, $jumlah): array
|
||||
{
|
||||
if ($dari === '' || $dari === null) {
|
||||
$dari = 0;
|
||||
}
|
||||
if ($jumlah === '' || $jumlah === null) {
|
||||
$jumlah = 25;
|
||||
}
|
||||
$dari = (int) $dari;
|
||||
$jumlah = (int) $jumlah;
|
||||
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->select('id_pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$rows = $this->db->table('aktifitas_harian')->where('pegawai', $pegawai->id_pegawai)->orderBy('waktu_aktifitas', 'DESC')->limit($jumlah, $dari)->get()->getResult();
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['data'] = $rows;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function savePp(string $token, string $nama_photo, string $img): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => 'Tidak ada jadwal kerja',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$dir = $this->ensureUploadDir('pengguna');
|
||||
$image = base64_decode($img);
|
||||
$image_name = uniqid((string) mt_rand(), true);
|
||||
$filename = $image_name . '-' . $nama_photo;
|
||||
|
||||
if ($image === false || $image === '' || file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $image) === false) {
|
||||
$data['pesan'] = 'Photo Kehadiran GAGAL upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ($pegawai->photo !== '' && $pegawai->photo !== null) {
|
||||
try {
|
||||
$photoPath = $dir . DIRECTORY_SEPARATOR . $pegawai->photo;
|
||||
if (is_file($photoPath)) {
|
||||
unlink($photoPath);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->table('pegawai')->where('id_pegawai', $pegawai->id_pegawai)->update([
|
||||
'photo' => $filename,
|
||||
]);
|
||||
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'Photo Profil berhasil di upload';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function savePassword(string $token, string $passlama, string $passbaru): array
|
||||
{
|
||||
$data = [
|
||||
'status' => 0,
|
||||
'pesan' => '-',
|
||||
];
|
||||
|
||||
$pegawai = $this->db->table('pegawai')->where('token', $token)->limit(1)->get()->getRow();
|
||||
if (! $pegawai) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (md5($passlama) == $pegawai->password) {
|
||||
$this->db->table('pegawai')->where('id_pegawai', $pegawai->id_pegawai)->update([
|
||||
'password' => md5($passbaru),
|
||||
]);
|
||||
$data['status'] = 1;
|
||||
$data['pesan'] = 'Password berhasil di ubah';
|
||||
} else {
|
||||
$data['pesan'] = 'Password lama tidak sesuai, silahkan coba lagi';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function generateRandomString(int $length = 10): string
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$randomString = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
// Sengaja memakai rand() seperti CI3 agar pola token kompatibel.
|
||||
$randomString .= $characters[rand(0, strlen($characters) - 1)];
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
private function ensureUploadDir(string $relativeUnderUploads): string
|
||||
{
|
||||
$base = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $relativeUnderUploads);
|
||||
if (! is_dir($base)) {
|
||||
mkdir($base, 0755, true);
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user