275 lines
8.7 KiB
PHP
275 lines
8.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Employee;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Attendance;
|
|
use App\Models\Location;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Carbon\Carbon;
|
|
|
|
class AttendanceController extends Controller
|
|
{
|
|
public function store(Request $request)
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'type' => 'required|in:in,out',
|
|
'latitude' => 'required|numeric|between:-90,90',
|
|
'longitude' => 'required|numeric|between:-180,180',
|
|
'photo' => 'required|image|mimes:jpeg,png,jpg|max:5120', // 5MB
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'message' => Str::ucfirst($validator->errors()->first()),
|
|
'data' => null
|
|
], 422);
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$today = Carbon::today()->toDateString();
|
|
|
|
$existingAttendance = Attendance::where('user_id', $user->id)
|
|
->where('date', $today)
|
|
->where('type', $request->type)
|
|
->whereNotIn('status', ['rejected'])
|
|
->first();
|
|
|
|
if ($existingAttendance) {
|
|
$typeText = $request->type == 'in' ? 'masuk' : 'keluar';
|
|
return response()->json([
|
|
'message' => "Anda sudah melakukan absen {$typeText} hari ini.",
|
|
'data' => null
|
|
], 422);
|
|
}
|
|
|
|
if ($request->type === 'out') {
|
|
$clockInRecord = Attendance::where('user_id', $user->id)
|
|
->where('date', $today)
|
|
->where('type', 'in')
|
|
->first();
|
|
|
|
if (!$clockInRecord) {
|
|
return response()->json([
|
|
'message' => 'Anda belum melakukan absen masuk hari ini.',
|
|
'data' => null
|
|
], 422);
|
|
}
|
|
|
|
$checkInTime = Carbon::parse($clockInRecord->time);
|
|
$now = Carbon::now();
|
|
$workDurationInMinutes = $checkInTime->diffInMinutes($now);
|
|
|
|
if ($workDurationInMinutes < 240) { // kurang dari 4 jam
|
|
$status = 'jam_kerja_kurang';
|
|
} elseif ($now->format('H:i') > '17:00') {
|
|
$status = 'lembur';
|
|
} else {
|
|
$status = 'accepted';
|
|
}
|
|
}
|
|
|
|
|
|
$photoPath = null;
|
|
if ($request->hasFile('photo')) {
|
|
$photo = $request->file('photo');
|
|
$filename = time() . '_' . $user->id . '_' . $request->type . '.' . $photo->getClientOriginalExtension();
|
|
$photoPath = $photo->storeAs('attendances', $filename, 'public');
|
|
}
|
|
|
|
$now = Carbon::now();
|
|
$status = 'accepted';
|
|
|
|
if ($request->type == 'in') {
|
|
$status = $now->format('H:i:s') > '08:00:00' ? 'late' : 'accepted';
|
|
} else if ($request->type == 'out') {
|
|
$clockIn = Attendance::where('user_id', $user->id)
|
|
->where('date', $today)
|
|
->where('type', 'in')
|
|
->first();
|
|
|
|
if ($clockIn) {
|
|
$clockInTime = Carbon::parse($clockIn->time);
|
|
$diffInHours = $clockInTime->diffInMinutes($now) / 60;
|
|
|
|
if ($diffInHours < 4) {
|
|
$status = 'jam_kerja_kurang';
|
|
} else {
|
|
$status = $now->format('H:i:s') > '17:00:00' ? 'lembur' : 'accepted';
|
|
}
|
|
} else {
|
|
$status = 'jam_kerja_kurang';
|
|
}
|
|
}
|
|
|
|
$nearestLocation = $this->findNearestLocation($request->latitude, $request->longitude);
|
|
if (!$nearestLocation) {
|
|
return response()->json([
|
|
'message' => 'Lokasi Anda di luar area yang diizinkan untuk absen.',
|
|
'data' => null
|
|
], 422);
|
|
}
|
|
|
|
$attendance = Attendance::create([
|
|
'user_id' => $user->id,
|
|
'date' => $today,
|
|
'type' => $request->type,
|
|
'time' => $now->format('H:i:s'),
|
|
'photo' => $photoPath,
|
|
'latitude' => $request->latitude,
|
|
'longitude' => $request->longitude,
|
|
'location_id' => $nearestLocation->id,
|
|
'status' => $status
|
|
]);
|
|
|
|
return response()->json([
|
|
'message' => 'Absensi berhasil dicatat.',
|
|
'data' => $attendance->load(['location'])
|
|
], 200);
|
|
}
|
|
|
|
// method lain tidak diubah
|
|
|
|
public function history(Request $request)
|
|
{
|
|
$user = auth()->user();
|
|
$data = Attendance::where('user_id', $user->id)->with(['location']);
|
|
|
|
if ($request->has('date') && !empty($request->start_date)) {
|
|
$data->where('date', '>=', $request->start_date);
|
|
}
|
|
|
|
if ($request->has('end_date') && !empty($request->end_date)) {
|
|
$data->where('date', '<=', $request->end_date);
|
|
}
|
|
|
|
if ($request->has('status') && !empty($request->status)) {
|
|
$data->where('status', $request->status);
|
|
}
|
|
|
|
if ($request->has('type') && !empty($request->type)) {
|
|
$data->where('type', $request->type);
|
|
}
|
|
|
|
$total_data = $data->get()->count();
|
|
$length = intval($request->input('length', 10));
|
|
$start = intval($request->input('start', 0));
|
|
|
|
$data = $data->orderBy("date", "desc")->orderBy("time", "desc");
|
|
if (!$length && !$start) {
|
|
$data = $data->get();
|
|
} else {
|
|
$data = $data->skip($start)->take($length)->get();
|
|
}
|
|
|
|
return response()->json([
|
|
'message' => 'Data berhasil diambil.',
|
|
'data' => $data,
|
|
'recordsTotal' => $total_data,
|
|
'recordsFiltered' => $total_data,
|
|
], 200);
|
|
}
|
|
|
|
public function todayStatus()
|
|
{
|
|
$user = auth()->user();
|
|
$today = Carbon::today()->toDateString();
|
|
|
|
$clockIn = Attendance::where('user_id', $user->id)
|
|
->where('date', $today)
|
|
->where('type', 'in')
|
|
->first();
|
|
|
|
$clockOut = Attendance::where('user_id', $user->id)
|
|
->where('date', $today)
|
|
->where('type', 'out')
|
|
->first();
|
|
|
|
return response()->json([
|
|
'message' => 'Status absensi hari ini berhasil diambil.',
|
|
'data' => [
|
|
'clock_in' => $clockIn,
|
|
'clock_out' => $clockOut,
|
|
'can_clock_in' => !$clockIn,
|
|
'can_clock_out' => $clockIn && !$clockOut
|
|
]
|
|
], 200);
|
|
}
|
|
|
|
public function checkLocation(Request $request)
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'latitude' => 'required|numeric|between:-90,90',
|
|
'longitude' => 'required|numeric|between:-180,180',
|
|
]);
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'valid' => false,
|
|
'message' => $validator->errors()->first(),
|
|
'data' => null
|
|
], 422);
|
|
}
|
|
$location = $this->findNearestLocation($request->latitude, $request->longitude);
|
|
if ($location) {
|
|
return response()->json([
|
|
'valid' => true,
|
|
'message' => 'Lokasi valid untuk check-in.',
|
|
'data' => $location
|
|
], 200);
|
|
} else {
|
|
return response()->json([
|
|
'valid' => false,
|
|
'message' => 'Lokasi Anda di luar area yang diizinkan untuk absen.',
|
|
'data' => null
|
|
], 200);
|
|
}
|
|
}
|
|
|
|
private function findNearestLocation($latitude, $longitude)
|
|
{
|
|
$locations = Location::all();
|
|
foreach ($locations as $location) {
|
|
$distance = $this->calculateDistance(
|
|
$latitude,
|
|
$longitude,
|
|
$location->center_lat,
|
|
$location->center_lng
|
|
);
|
|
if ($distance * 1000 <= $location->radius) {
|
|
return $location;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function calculateDistance($lat1, $lon1, $lat2, $lon2)
|
|
{
|
|
$earthRadius = 6371;
|
|
|
|
$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 areas()
|
|
{
|
|
$locations = Location::all()->map(function($loc) {
|
|
return [
|
|
'id' => $loc->id,
|
|
'center_lat' => (float) $loc->center_lat,
|
|
'center_lng' => (float) $loc->center_lng,
|
|
'radius' => (float) $loc->radius,
|
|
'name' => $loc->name ?? null,
|
|
];
|
|
});
|
|
return response()->json($locations, 200);
|
|
}
|
|
}
|