ping(); return true; } catch (\Throwable) { return false; } } private function appendDecodedRequest(array &$target, ?string $raw): void { if (!$raw) { return; } $request = json_decode($raw); if ($request && ($request->function ?? null) !== 'rejected') { $target[] = $request; } } private function appendDecodedRequestWithAttendance(array &$target, ?string $raw, ?Attendance $attendance = null): void { if (!$raw) { return; } $request = json_decode($raw); if ($request && ($request->function ?? null) !== 'rejected') { $target[] = $this->enrichRequestForView($request, $attendance); } } private function buildFallbackRequestFromAttendance(Attendance $attendance): object { $request = (object) [ 'function' => 'edit', 'userid' => $attendance->user_identity, 'id' => $attendance->id, 'oldenter' => $attendance->entered_at ? Carbon::parse($attendance->entered_at)->format('H:i') : '', 'newenter' => '', 'oldexit' => $attendance->exited_at ? Carbon::parse($attendance->exited_at)->format('H:i') : '', 'newexit' => '', 'date' => $attendance->entered_at ? Jalalian::fromCarbon(Carbon::parse($attendance->entered_at))->format('Y/m/d') : null, ]; $request->display_date = $this->formatRequestDisplayDate($request, $attendance); return $request; } private function formatRequestDisplayDate(object $request, ?Attendance $attendance = null): string { $candidates = array_filter([ $attendance?->entered_at, (!empty($request->oldenter) && str_contains((string) $request->oldenter, ' ')) ? $request->oldenter : null, $request->date ?? null, ]); foreach ($candidates as $value) { try { if (is_string($value) && preg_match('/^\d{4}\/\d{2}\/\d{2}$/', $value)) { $year = (int) substr($value, 0, 4); if ($year >= 1700) { return Jalalian::fromCarbon(Carbon::createFromFormat('Y/m/d', $value))->format('Y/m/d'); } if ($year >= 1300) { return Jalalian::fromFormat('Y/m/d', $value)->format('Y/m/d'); } } $carbon = Carbon::parse($value); if ($carbon->year >= 1000) { return Jalalian::fromCarbon($carbon)->format('Y/m/d'); } } catch (\Throwable) { continue; } } return '-'; } private function enrichRequestForView(object $request, ?Attendance $attendance = null): object { if (!isset($request->display_date)) { $request->display_date = $this->formatRequestDisplayDate($request, $attendance); } return $request; } private function resolveRequestTime(?string $preferred, ?string $fallback, $recordDefault = null): ?string { $value = ($preferred !== null && $preferred !== '') ? $preferred : $fallback; if ($value === null || $value === '') { if ($recordDefault) { return Carbon::parse($recordDefault)->format('H:i:s'); } return null; } return Carbon::parse($value)->format('H:i:s'); } private function buildAttendanceTimestamp(string $date, ?string $time): ?string { if (!$time) { return null; } return $date . ' ' . $time; } private function decrementUserRequestCount(?string $userName): void { if (!$userName) { return; } User::where('user_name', $userName) ->where('req_counts', '>', 0) ->decrement('req_counts'); } private function loadPendingRequests($users): array { $userNames = $users->pluck('user_name')->filter()->values(); $redisAvailable = $this->redisIsAvailable(); $pendingByUser = Attendance::query() ->whereIn('user_identity', $userNames) ->whereNotNull('requested') ->where('requested', '!=', '') ->get(['id', 'user_identity', 'entered_at', 'exited_at']) ->groupBy('user_identity'); $records = []; foreach ($users as $user) { $rec = [ 'user_name' => $user->user_name, 'name' => $user->name, 'attendances' => [], ]; $pendingForUser = $pendingByUser->get($user->user_name, collect()); $hasRequestHint = ($user->req_counts ?? 0) > 0 || $pendingForUser->isNotEmpty(); if (!$hasRequestHint) { $records[] = $rec; continue; } $loadedIds = []; if ($redisAvailable) { foreach (Redis::lrange("att-tempreq:creates:{$user->user_name}", 0, -1) as $reqJson) { $this->appendDecodedRequestWithAttendance($rec['attendances'], $reqJson); } $this->appendDecodedRequestWithAttendance($rec['attendances'], Redis::get("att-tempreq:{$user->user_name}:new")); if ($pendingForUser->isNotEmpty()) { $redisKeys = $pendingForUser ->map(fn ($attendance) => "att-tempreq:{$user->user_name}:{$attendance->id}") ->values() ->all(); $rawValues = Redis::mget($redisKeys); foreach ($pendingForUser->values() as $index => $attendance) { $raw = $rawValues[$index] ?? null; if (!$raw) { // Redis موجود است ولی کلید نیست → درخواست قبلاً تایید/رد شده؛ پرچم کهنه را پاک کن $attendance->requested = null; $attendance->save(); continue; } $decoded = json_decode($raw); if (($decoded->function ?? null) === 'rejected') { Redis::del("att-tempreq:{$user->user_name}:{$attendance->id}"); $attendance->requested = null; $attendance->save(); continue; } $beforeCount = count($rec['attendances']); $this->appendDecodedRequestWithAttendance($rec['attendances'], $raw, $attendance); if (count($rec['attendances']) > $beforeCount) { $loadedIds[(string) $attendance->id] = true; } } } } // فقط وقتی Redis در دسترس نیست از fallback استفاده کن if (!$redisAvailable) { foreach ($pendingForUser as $attendance) { if (isset($loadedIds[(string) $attendance->id])) { continue; } $rec['attendances'][] = $this->buildFallbackRequestFromAttendance($attendance); } } $pendingById = $pendingForUser->keyBy('id'); foreach ($rec['attendances'] as $index => $request) { $attendance = $pendingById->get($request->id ?? null); $rec['attendances'][$index] = $this->enrichRequestForView($request, $attendance); } $records[] = $rec; } return $records; } private function removeCreateRequest(string $userid, object $data): void { $key = "att-tempreq:creates:{$userid}"; try { $items = Redis::lrange($key, 0, -1); } catch (\Throwable) { return; } foreach ($items as $item) { $decoded = json_decode($item); if (!$decoded) { continue; } $sameUser = ($decoded->userid ?? null) === ($data->userid ?? null); $sameEnter = (string) ($decoded->oldenter ?? '') === (string) ($data->oldenter ?? ''); $sameExit = (string) ($decoded->oldexit ?? '') === (string) ($data->oldexit ?? ''); if ($sameUser && $sameEnter && $sameExit) { Redis::lrem($key, 1, $item); } } Redis::del("att-tempreq:{$userid}:new"); } private function clearAttendanceRequestFlag($attendanceId): void { if (!$attendanceId || $attendanceId === 'new') { return; } $att = Attendance::find($attendanceId); if ($att) { $att->requested = null; $att->save(); } } private function enrichUsersForDashboard($users): void { $userNames = $users->pluck('user_name'); $now = Carbon::now(); $monthStart = $now->copy()->startOfMonth(); $monthEnd = $now->copy()->endOfMonth(); $today = $now->format('Y-m-d'); $monthAttendances = Attendance::query() ->whereIn('user_identity', $userNames) ->whereBetween('entered_at', [$monthStart, $monthEnd]) ->whereNotNull('duration') ->select(['user_identity', 'duration']) ->get() ->groupBy('user_identity'); $openAttendances = Attendance::query() ->whereIn('user_identity', $userNames) ->whereNull('exited_at') ->select(['id', 'user_identity', 'entered_at']) ->get() ->groupBy('user_identity'); $activeBreakAttendanceIds = AttendanceBreak::query() ->whereIn('attendance_id', $openAttendances->flatten()->pluck('id')) ->whereNull('break_end') ->pluck('attendance_id') ->flip(); foreach ($users as $user) { $totalSeconds = 0; foreach ($monthAttendances->get($user->user_name, []) as $attendance) { if (!$attendance->duration) { continue; } $parts = explode(':', $attendance->duration); if (count($parts) !== 3) { continue; } [$h, $m, $s] = array_map('intval', $parts); $totalSeconds += ($h * 3600) + ($m * 60) + $s; } $hours = intdiv($totalSeconds, 3600); $minutes = intdiv($totalSeconds % 3600, 60); $user->totaltime = sprintf('%02d:%02d', $hours, $minutes); [$jMonthStart, $jMonthEnd] = app(LatePenaltyService::class)->jalaliMonthBounds($now); $user->monthly_late_fine = app(LatePenaltyService::class) ->monthlyTotalFine($user->user_name, $jMonthStart, $jMonthEnd); $user->working = 0; $openForUser = $openAttendances->get($user->user_name, collect()); if ($openForUser->isEmpty()) { continue; } $latestOpen = $openForUser->sortByDesc('entered_at')->first(); if ($latestOpen && Carbon::parse($latestOpen->entered_at)->format('Y-m-d') === $today) { $user->working = 1; } foreach ($openForUser as $openAttendance) { if ($activeBreakAttendanceIds->has($openAttendance->id)) { $user->working = 2; break; } } } } public function dashboard() { if (isset($_COOKIE['token-0'])) { $uname = $_COOKIE['token-0']; setcookie("user_identity", $_COOKIE['token-0'], time() + (60 * 60 * 24 * 365), '/'); Cookie::queue(Cookie::forget('token-0')); } else { $uname = $_COOKIE['user_identity'] ?? null; } if (!$uname) { return redirect('/')->with('message', [ 'color' => '#ff8800ff', 'text' => 'ابتدا باید وارد شوید!' ]); } $u = User::select('name', 'is_admin') ->where('user_name', $uname) ->first(); if (!$u) { return redirect('/')->with('message', [ 'color' => '#ff8800ff', 'text' => 'کاربر یافت نشد!' ]); } if ($u->is_admin == 1) { $provider = 'admin'; } elseif ($u->is_admin == 2) { $provider = 'supervisor'; } if ($u->is_admin == '1' || $u->is_admin == '2') { setcookie('ad', '1', time() + (60 * 60 * 24 * 365 * 5), "/"); $users = User::whereNotNull('user_name') ->select(['id', 'name', 'user_name', 'req_counts', 'active_task', 'work_start_time']) ->get(); $this->enrichUsersForDashboard($users); $users = $users->sortByDesc('working'); $records = $this->loadPendingRequests($users); $leaveRequests = LeaveRequest::query() ->where('status', 'pending') ->with('user:id,name,user_name') ->orderByDesc('created_at') ->get(); return view("admin..admin")->with(['message', [ 'color' => '#ff8800ff', "text" => 'شما به پنل ادمین وارد شدید✔️' ], "users" => $users, 'reqs' => $records, 'leaveRequests' => $leaveRequests, "userType" => $provider ]); } return redirect()->back()->with('message', [ 'color' => '#ff8800ff', "text" => 'شما دسترسی به این صفحه را ندارید!' ]); } public function newUser() { // dd('hello world'); return view('admin.new'); } public function addUser(Request $request) { // dd($request); try { $user = new User(); $user->name = $request->name; $user->email = $request->email; $user->user_name = $request->username; $user->chatid = $request->chatid; $user->number = $request->number; $user->save(); return redirect(route('admin.dashboard'))->with('message', [ 'color' => '#009900ff', 'text' => 'کاربر با موفقیت ایجاد شد✔️' ]); } catch (\Exception $exception) { return redirect(route('admin.dashboard'))->with('message', [ 'color' => '#ff9900ff', 'text' => 'مقادیر وارد شده تکراری یا اشتباه است✖️' ]); } } public function edit(Request $request) { // dd($request); $allData = array_merge($request->all(), $request->query()); $converted = $this->fa2en_numbers($allData); $request->merge($converted); $id = $request->id; $nstartt = $request->starttime; $nendt = $request->endtime; // dd($request); $user = User::where('user_name', $_COOKIE['user_identity'])->first(); if ($user->is_admin == 2) { $attController = new AttendanceController(); return $attController->edit($request); } if ($id == 'new') { $user_name = $request->uname; if ($request->display_date == null) { $date = Carbon::now()->format('Y-m-d'); } else { // dump($date); $date = \Morilog\Jalali\CalendarUtils::convertNumbers($request->display_date, true); $date = str_replace('/', '-', $date); // dump($date); $date = Jalalian::fromFormat('Y-m-d', $date)->toCarbon()->format('Y-m-d'); // $date = \Morilog\Jalali\CalendarUtils::createCarbonFromFormat('Y-m-d', $date)->format('Y-m-d'); } if (($nendt > $nstartt) || ($nendt === null && $nstartt !== null)) { $attendance = new Attendance(); $attendance->ip_address = 'admin-manual'; $attendance->user_identity = $user_name; $attendance->entered_at = $nstartt ? $date . ' ' . $nstartt : null; $attendance->exited_at = $nendt ? $date . ' ' . $nendt : null; $attendance->duration = $nendt !== null ? $attendance->duration($nstartt, $nendt) : null; $attendance->save(); app(LatePenaltyService::class)->syncAttendance($attendance); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', "text" => 'تغییرات با موفقیت ذخیره شد.✔️' ]); } else { return redirect()->back()->with('message', [ 'color' => '#ff4400ff', "text" => 'مقادیر اشتباه وارد شده است✖️' ]); } } else { $attendance = Attendance::find($id); $date = Carbon::parse($attendance->entered_at ?? $attendance->exited_at)->format('Y-m-d'); $ostartt = Carbon::parse($attendance->entered_at)->format('H:i'); $oendt = Carbon::parse($attendance->exited_at)->format('H:i'); if (($ostartt != $nstartt || $oendt != $nendt) && ($nendt != null ? $nendt > $nstartt : true)) { $attendance->entered_at = $date . ' ' . $nstartt; $attendance->exited_at = $nendt ? $date . ' ' . $nendt : null; $attendance->duration = $nendt ? $attendance->duration($nstartt, $nendt) : null; $attendance->save(); app(LatePenaltyService::class)->syncAttendance($attendance); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', "text" => 'تغییرات با موفقیت ذخیره شد.✔️' ]); } else { return redirect()->back()->with('message', [ 'color' => '#ff4400ff', "text" => 'مقادیر اشتباه وارد شده است✖️' ]); } } } public function delete(Request $request) { // dd($request); $id = $request->id; $attendance = Attendance::find($id); if ($attendance) { $attendance->delete(); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', "text" => 'حذف با موفقیت انجام شد.✔️' ]); } else { return redirect()->back()->with('message', [ 'color' => '#ff4400ff', "text" => 'خطا در حذف رکورد✖️' ]); } } public function update(Request $request) { // dd($request); $data = json_decode($request->data); if ($request->update == 'approve') { if ($data->function == 'delete') { $st = Attendance::find($data->id); if ($st) { $st->delete(); if ($st) { tel_notif($st->user_identity, "تایید"); // $create_requests_key = "att-tempreq:creates:{$data->userid}"; // Redis::connection()->lrem($create_requests_key, 1, json_encode($data)); Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}"); $this->decrementUserRequestCount($data->userid); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', 'text' => 'رکورد با موفقیت حذف شد✔️' ]); } else { return redirect()->back()->with('message', [ 'color' => '#ff8800ff', 'text' => 'حذف رکورد با شکست مواجه شد✖️️' ]); } } else { return redirect()->back()->with('message', [ 'color' => '#ff8800ff', 'text' => 'رکورد پیدا نشد✖️' ]); } } elseif ($data->function == 'edit') { $record = Attendance::find($data->id); if (!$record) { return redirect()->back()->with('message', [ 'color' => '#ff8800ff', 'text' => 'رکورد پیدا نشد✖️' ]); } $attendance = new Attendance(); $date = Carbon::parse($record->entered_at)->format('Y-m-d'); $enterTime = $this->resolveRequestTime($data->newenter ?? null, $data->oldenter ?? null, $record->entered_at); $exitTime = $this->resolveRequestTime($data->newexit ?? null, $data->oldexit ?? null, $record->exited_at); $record->ip_address = 'admin-edited'; $record->user_identity = $data->userid; $record->entered_at = $this->buildAttendanceTimestamp($date, $enterTime) ?? $record->entered_at; if ($exitTime) { $record->exited_at = $this->buildAttendanceTimestamp($date, $exitTime); $duration = $attendance->duration($enterTime, $exitTime); } else { $record->exited_at = null; $duration = null; } $break = AttendanceBreak::where('attendance_id', $data->id)->sum('duration'); if ($break && $duration) { $breakSec = strtotime("1970-01-01 " . $break) - strtotime("1970-01-01 00:00:00"); $durationSec = strtotime("1970-01-01 " . $duration) - strtotime("1970-01-01 00:00:00"); $durationSec -= $breakSec; $duration = gmdate("H:i:s", $durationSec); } $record->duration = $duration ?? $record->duration; $record->requested = null; $record->save(); app(LatePenaltyService::class)->syncAttendance($record); Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}"); $this->decrementUserRequestCount($data->userid); tel_notif($record->user_identity, "تایید"); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', "text" => 'رکورد با موفقیت ذخیره شد.✔️' ]); } elseif ($data->function == 'new') { $enteredAt = !empty($data->oldenter) ? Carbon::parse($data->oldenter) : null; $exitedAt = !empty($data->oldexit) ? Carbon::parse($data->oldexit) : null; if (!$enteredAt) { return redirect()->back()->with('message', [ 'color' => '#ff8800ff', 'text' => 'زمان ورود در درخواست مشخص نیست✖️' ]); } $starttime = $enteredAt->format('H:i:s'); $endtime = $exitedAt?->format('H:i:s'); if (is_null($endtime) || $endtime > $starttime) { $attendance = new Attendance(); $attendance->ip_address = 'admin-new'; $attendance->user_identity = $data->userid; $attendance->entered_at = $enteredAt->format('Y-m-d H:i:s'); $attendance->exited_at = $exitedAt?->format('Y-m-d H:i:s'); $attendance->duration = $endtime ? $attendance->duration($starttime, $endtime) : null; $attendance->save(); app(LatePenaltyService::class)->syncAttendance($attendance); $this->removeCreateRequest($data->userid, $data); $this->decrementUserRequestCount($data->userid); tel_notif($data->userid, "تایید"); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', "text" => 'درخواست با موفقیت تایید شد.✔️' ]); } } } if ($request->update == 'reject') { if ($data->function == 'delete' || $data->function == 'edit') { Redis::connection()->del("att-tempreq:{$data->userid}:{$data->id}"); $this->clearAttendanceRequestFlag($data->id ?? null); } elseif ($data->function == 'new') { $this->removeCreateRequest($data->userid, $data); } tel_notif($data->userid, "رد"); $this->decrementUserRequestCount($data->userid); return redirect()->back()->with('message', [ 'color' => '#6aff00ff', "text" => 'درخواست با موفقیت رد شد.✔️' ]); } } // private function fa2en_numbers($input) // { // if (is_string($input)) { // $fa_numbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; // $en_numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; // return str_replace($fa_numbers, $en_numbers, $input); // } elseif (is_array($input)) { // return array_map([$this, __FUNCTION__], $input); // } else { // return $input; // } // } } function tel_notif($username, $status) { $message = "درخواست شما {$status} شد."; $user = User::where('user_name', $username)->whereNotNull('chatid')->select('chatid', 'user_name')->first(); if ($user) { try { Http::withoutVerifying()->post('https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [ "chat_id" => $user->bale, 'text' => $message, ]); } catch (\Exception $e) {} } }