342 lines
13 KiB
PHP
342 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Enums\JenisPengurangan;
|
|
use App\Enums\StatusPengajuan;
|
|
use App\Enums\StatusPresensi;
|
|
use App\Enums\StatusValidasi;
|
|
use App\Helpers\ApiResponse;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Presensi;
|
|
use App\Services\PresensiService;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use App\Models\PenggunaanPoin;
|
|
use Carbon\Carbon;
|
|
|
|
class PresensiController extends Controller
|
|
{
|
|
protected $presensiService;
|
|
|
|
public function __construct(PresensiService $presensiService)
|
|
{
|
|
$this->presensiService = $presensiService;
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
$user = Auth::user();
|
|
$today = Carbon::now('Asia/Jakarta')->toDateString();
|
|
|
|
$jadwal = \App\Models\JadwalKerja::with(['shift', 'user'])
|
|
->where('id_user', $user->id)
|
|
->where('tanggal', $today)
|
|
->first();
|
|
|
|
if (!$jadwal) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Tidak ada jadwal hari ini'
|
|
], 404);
|
|
}
|
|
|
|
$jamMasuk = $jadwal->shift->jam_mulai;
|
|
$jamPulang = $jadwal->shift->jam_selesai;
|
|
$infoJadwal = $jadwal->shift->nama_shift;
|
|
|
|
$poinOverrides = PenggunaanPoin::where('id_user', $user->id)
|
|
->whereDate('tanggal_penggunaan', $today)
|
|
->where('id_status', StatusPengajuan::DISETUJUI)
|
|
->get();
|
|
|
|
foreach ($poinOverrides as $poin) {
|
|
if ($poin->id_pengurangan == JenisPengurangan::MASUK_SIANG_POIN && $poin->jam_masuk_custom) {
|
|
$jamMasuk = substr($poin->jam_masuk_custom, 0, 5) . ':00';
|
|
$infoJadwal .= ' (Masuk Siang)';
|
|
}
|
|
if ($poin->id_pengurangan == JenisPengurangan::PULANG_CEPAT_POIN && $poin->jam_pulang_custom) {
|
|
$jamPulang = substr($poin->jam_pulang_custom, 0, 5) . ':00';
|
|
$infoJadwal .= ' (Pulang Cepat)';
|
|
}
|
|
}
|
|
|
|
$presensi = Presensi::where('id_user', $user->id)
|
|
->where('tanggal', $today)
|
|
->first();
|
|
|
|
$ketMasuk = 'Belum Absen';
|
|
$ketPulang = null;
|
|
|
|
if ($presensi) {
|
|
if ($presensi->waktu_terlambat) {
|
|
$ketMasuk = 'Terlambat';
|
|
} else if ($presensi->waktu_masuk_awal) {
|
|
$ketMasuk = (str_contains($infoJadwal, 'Poin')) ? 'Masuk Siang (Poin) - Datang Awal' : 'Datang Awal';
|
|
} else {
|
|
$ketMasuk = (str_contains($infoJadwal, 'Poin')) ? 'Masuk Siang (Poin)' : 'Tepat Waktu';
|
|
}
|
|
|
|
if ($presensi->jam_pulang) {
|
|
if ($presensi->waktu_pulang_awal) {
|
|
$ketPulang = 'Pulang Awal';
|
|
} else if ($presensi->waktu_pulang_akhir) {
|
|
$ketPulang = (str_contains($infoJadwal, 'Poin')) ? 'Pulang Cepat (Poin) - Lembur' : 'Lembur / Pulang Akhir';
|
|
} else {
|
|
$ketPulang = (str_contains($infoJadwal, 'Poin')) ? 'Pulang Cepat (Poin)' : 'Tepat Waktu';
|
|
}
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'jadwal' => [
|
|
'nama_shift' => $infoJadwal,
|
|
'jam_masuk' => $jamMasuk,
|
|
'jam_pulang' => $jamPulang,
|
|
'tanggal' => Carbon::parse($today)->translatedFormat('l, d F Y'),
|
|
],
|
|
'presensi' => [
|
|
'jam_masuk' => $presensi ? $presensi->jam_masuk : null,
|
|
'jam_pulang' => $presensi ? $presensi->jam_pulang : null,
|
|
'foto_masuk' => $presensi ? $presensi->foto_masuk : null,
|
|
'status_validasi' => $presensi ? $presensi->id_validasi : null,
|
|
'keterangan' => $presensi ? $ketMasuk : null,
|
|
'keterangan_pulang' => $presensi ? $ketPulang : null,
|
|
]
|
|
]
|
|
]);
|
|
}
|
|
|
|
public function checkRadius(Request $request)
|
|
{
|
|
try {
|
|
$request->validate([
|
|
'latitude' => 'required|numeric',
|
|
'longitude' => 'required|numeric',
|
|
]);
|
|
|
|
$user = Auth::user();
|
|
$kantor = $user->kantor;
|
|
|
|
if (!$kantor) {
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => 'Data kantor belum disetting.',
|
|
], 400);
|
|
}
|
|
|
|
$distance = $this->calculateDistance(
|
|
$request->latitude,
|
|
$request->longitude,
|
|
$kantor->latitude,
|
|
$kantor->longitude,
|
|
);
|
|
|
|
$isWithinRadius = $distance <= $kantor->radius;
|
|
|
|
return response()->json([
|
|
'status' => 'success',
|
|
'data' => [
|
|
'is_within_radius' => $isWithinRadius,
|
|
'distance' => round($distance, 2),
|
|
'radius' => $kantor->radius,
|
|
]
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
private function calculateDistance($lat1, $lon1, $lat2, $lon2)
|
|
{
|
|
$earthRadius = 6371000;
|
|
$dLat = deg2rad($lat2 - $lat1);
|
|
$dLon = deg2rad($lon2 - $lon1);
|
|
$a = sin($dLat / 2) * sin($dLat / 2) +
|
|
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
|
|
sin($dLon / 2) * sin($dLon / 2);
|
|
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
return $earthRadius * $c;
|
|
}
|
|
|
|
public function store(Request $request)
|
|
{
|
|
try {
|
|
$request->validate([
|
|
'latitude' => 'required|numeric|between:-90,90',
|
|
'longitude' => 'required|numeric|between:-180,180',
|
|
'foto' => 'required|image|max:2048',
|
|
'status' => 'required|in:masuk,pulang',
|
|
'keterangan_luar_radius' => 'nullable|string|max:500',
|
|
'alasan_telat' => 'nullable|string|max:500',
|
|
]);
|
|
|
|
$user = Auth::user();
|
|
|
|
if ($request->status == 'masuk') {
|
|
$data = $this->presensiService->absenMasuk($user, $request);
|
|
return ApiResponse::success($data, 'Absen masuk berhasil');
|
|
} else {
|
|
$data = $this->presensiService->absenPulang($user, $request);
|
|
return ApiResponse::success($data, 'Absen pulang berhasil');
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$code = (int) $e->getCode();
|
|
if ($code < 100 || $code > 599)
|
|
$code = 500;
|
|
return ApiResponse::error($e->getMessage(), $code);
|
|
}
|
|
}
|
|
|
|
public function history(Request $request)
|
|
{
|
|
try {
|
|
$userId = Auth::id();
|
|
$month = $request->get('month', Carbon::now('Asia/Jakarta')->month);
|
|
$year = $request->get('year', Carbon::now('Asia/Jakarta')->year);
|
|
|
|
$history = Presensi::where('id_user', $userId)
|
|
->whereMonth('tanggal', $month)
|
|
->whereYear('tanggal', $year)
|
|
->latest('tanggal')
|
|
->get();
|
|
|
|
$enriched = $history->map(function ($item) use ($userId) {
|
|
$jadwal = \App\Models\JadwalKerja::with('shift')
|
|
->where('id_user', $userId)
|
|
->where('tanggal', $item->tanggal)
|
|
->first();
|
|
|
|
$namaShift = $jadwal?->shift?->nama_shift ?? '-';
|
|
|
|
$statusMasuk = '-';
|
|
if ($item->jam_masuk) {
|
|
if ($item->id_status == StatusPresensi::ALPHA) {
|
|
$statusMasuk = 'Alpha (Batal)';
|
|
} elseif ($item->waktu_terlambat) {
|
|
$statusMasuk = 'Terlambat';
|
|
} elseif ($item->waktu_masuk_awal) {
|
|
$statusMasuk = 'Datang Awal';
|
|
} else {
|
|
$statusMasuk = 'Tepat Waktu';
|
|
}
|
|
} else {
|
|
if ($item->alasan_telat && str_contains($item->alasan_telat, 'Cuti')) {
|
|
$statusMasuk = 'Cuti';
|
|
} elseif ($item->id_status == StatusPresensi::IZIN) {
|
|
$statusMasuk = 'Izin';
|
|
} elseif ($item->id_status == StatusPresensi::SAKIT) {
|
|
$statusMasuk = 'Sakit';
|
|
} elseif ($item->id_status != null && $item->id_status != StatusPresensi::TEPAT_WAKTU && $item->id_status != StatusPresensi::TERLAMBAT) {
|
|
|
|
$statusStatus = \App\Enums\StatusPresensi::label($item->id_status);
|
|
if ($statusStatus !== 'Unknown') {
|
|
$statusMasuk = $statusStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
$statusPulang = null;
|
|
if ($item->jam_pulang) {
|
|
if ($item->waktu_pulang_awal) {
|
|
$statusPulang = 'Pulang Awal';
|
|
} elseif ($item->waktu_pulang_akhir) {
|
|
$statusPulang = 'Lembur';
|
|
} else {
|
|
$statusPulang = 'Tepat Waktu';
|
|
}
|
|
}
|
|
|
|
$totalJam = '-';
|
|
if ($item->jam_masuk && $item->jam_pulang) {
|
|
try {
|
|
$masuk = Carbon::parse($item->tanggal . ' ' . $item->jam_masuk);
|
|
$pulang = Carbon::parse($item->tanggal . ' ' . $item->jam_pulang);
|
|
|
|
if ($pulang->lessThan($masuk)) {
|
|
$pulang->addDay();
|
|
}
|
|
|
|
$diffMinutes = abs($pulang->diffInMinutes($masuk));
|
|
$hours = floor($diffMinutes / 60);
|
|
$minutes = $diffMinutes % 60;
|
|
$totalJam = $hours . 'j ' . $minutes . 'm';
|
|
} catch (\Exception $e) {
|
|
$totalJam = '-';
|
|
}
|
|
} elseif ($item->jam_masuk && !$item->jam_pulang && $item->id_status == StatusPresensi::ALPHA) {
|
|
$totalJam = '0j 0m (Batal)';
|
|
}
|
|
|
|
return [
|
|
'id_presensi' => $item->id_presensi,
|
|
'tanggal' => Carbon::parse($item->tanggal)->translatedFormat('l, d F Y'),
|
|
'tanggal_raw' => $item->tanggal,
|
|
'jam_masuk' => $item->jam_masuk,
|
|
'jam_pulang' => $item->jam_pulang,
|
|
'shift' => $namaShift,
|
|
'status_masuk' => $statusMasuk,
|
|
'status_pulang' => $statusPulang,
|
|
'total_jam' => $totalJam,
|
|
'waktu_terlambat' => $item->waktu_terlambat,
|
|
'keterangan_luar_radius' => $item->keterangan_luar_radius,
|
|
'verifikasi_wajah' => (bool) $item->verifikasi_wajah,
|
|
'status_validasi' => $item->id_validasi,
|
|
'alasan_penolakan' => $item->alasan_penolakan,
|
|
'foto_masuk_url' => $item->foto_wajah_masuk
|
|
? asset('storage/' . $item->foto_wajah_masuk)
|
|
: null,
|
|
'foto_pulang_url' => $item->foto_wajah_pulang
|
|
? asset('storage/' . $item->foto_wajah_pulang)
|
|
: null,
|
|
];
|
|
});
|
|
|
|
return ApiResponse::success([
|
|
'data' => $enriched->values(),
|
|
'month' => (int) $month,
|
|
'year' => (int) $year,
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
return ApiResponse::error($e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
public function resubmit(Request $request, $id)
|
|
{
|
|
try {
|
|
$request->validate([
|
|
'keterangan' => 'required|string|max:500',
|
|
]);
|
|
|
|
$presensi = Presensi::where('id_presensi', $id)
|
|
->where('id_user', Auth::id())
|
|
->firstOrFail();
|
|
|
|
if ($presensi->id_validasi != StatusValidasi::DITOLAK) {
|
|
return ApiResponse::error('Hanya presensi yang ditolak yang bisa diajukan ulang.', 422);
|
|
}
|
|
|
|
$presensi->update([
|
|
'id_validasi' => StatusValidasi::PENDING,
|
|
'alasan_penolakan' => null,
|
|
'keterangan_luar_radius' => $request->keterangan,
|
|
]);
|
|
|
|
return ApiResponse::success(null, 'Presensi berhasil diajukan ulang. Menunggu validasi admin.');
|
|
|
|
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
|
return ApiResponse::error('Data presensi tidak ditemukan.', 404);
|
|
} catch (\Exception $e) {
|
|
return ApiResponse::error($e->getMessage(), 500);
|
|
}
|
|
}
|
|
}
|