Files

733 lines
27 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers;
use App\Models\LeaveRequest;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Redis;
use Illuminate\Console\View\Components\Task;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Response;
use App\Models\Attendance;
use \App\Models\AttendanceBreak;
use App\Models\ai;
use Illuminate\Support\Facades\Http;
use Morilog\Jalali\Jalalian;
use GuzzleHttp\Client;
use App\Models\Tasks;
use App\Models\Pivot_Tasks;
use App\Services\LatePenaltyService;
class AdminController extends Controller
{
private function redisIsAvailable(): bool
{
try {
Redis::connection()->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) {}
}
}