Editing Employee Attendance and Check-ins
This commit is contained in:
@@ -401,7 +401,7 @@ class AdminController extends Controller
|
||||
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'])
|
||||
->select(['id', 'name', 'user_name', 'req_counts', 'active_task', 'work_start_time'])
|
||||
->get();
|
||||
|
||||
$this->enrichUsersForDashboard($users);
|
||||
|
||||
@@ -609,18 +609,38 @@ class AttendanceController extends Controller
|
||||
|
||||
}
|
||||
|
||||
private function viewerIsAdmin(): bool
|
||||
{
|
||||
$viewer = User::where('user_name', $_COOKIE['user_identity'] ?? '')->first();
|
||||
|
||||
return $viewer && in_array((string) $viewer->is_admin, ['1', '2'], true);
|
||||
}
|
||||
|
||||
public function profile(Request $request)
|
||||
{
|
||||
$username = $request->query('username') ?: $_COOKIE['user_identity'];
|
||||
$user = User::where('user_name', $username)->first();
|
||||
|
||||
return view('attendance.profile', ['user' => $user]);
|
||||
return view('attendance.profile', [
|
||||
'user' => $user,
|
||||
'viewerIsAdmin' => $this->viewerIsAdmin(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateProfile(Request $request)
|
||||
{
|
||||
// dd($request->all());
|
||||
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
$viewerIsAdmin = $this->viewerIsAdmin();
|
||||
$targetUsername = ($viewerIsAdmin && $request->filled('target_username'))
|
||||
? $request->input('target_username')
|
||||
: $_COOKIE['user_identity'];
|
||||
$user = User::where('user_name', $targetUsername)->first();
|
||||
|
||||
if (!$user) {
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'کاربر یافت نشد!'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($user->password != $request->password) {
|
||||
$request->validate([
|
||||
@@ -645,14 +665,53 @@ class AttendanceController extends Controller
|
||||
$user->is_admin = $request->role == 1 ? 1 : null;
|
||||
}
|
||||
|
||||
$workStartChanged = false;
|
||||
if ($viewerIsAdmin && $request->has('work_start_time')) {
|
||||
$raw = trim((string) $this->fa2en_numbers($request->input('work_start_time')));
|
||||
if ($raw === '') {
|
||||
$newStart = null;
|
||||
} elseif (preg_match('/^([01]?\d|2[0-3]):([0-5]\d)$/', $raw, $m)) {
|
||||
$newStart = sprintf('%02d:%02d:00', (int) $m[1], (int) $m[2]);
|
||||
} else {
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#ff4400ff',
|
||||
'text' => 'ساعت شروع کار نامعتبر است (مثال: 15:00).'
|
||||
]);
|
||||
}
|
||||
|
||||
$oldStart = $user->work_start_time ? substr((string) $user->work_start_time, 0, 8) : null;
|
||||
if ($oldStart !== $newStart) {
|
||||
$user->work_start_time = $newStart;
|
||||
$workStartChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
if ($workStartChanged) {
|
||||
$this->resyncCurrentMonthLate($user->user_name);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('message', [
|
||||
'color' => '#00c300ff',
|
||||
'text' => 'اطلاعات با موفقیت ویرایش شد.'
|
||||
]);
|
||||
}
|
||||
|
||||
private function resyncCurrentMonthLate(string $userIdentity): void
|
||||
{
|
||||
$service = app(LatePenaltyService::class);
|
||||
$service->forgetWorkStart($userIdentity);
|
||||
[$monthStart, $monthEnd] = $service->jalaliMonthBounds(Carbon::now());
|
||||
|
||||
Attendance::query()
|
||||
->where('user_identity', $userIdentity)
|
||||
->whereBetween('entered_at', [$monthStart, $monthEnd])
|
||||
->orderBy('entered_at')
|
||||
->get()
|
||||
->each(fn (Attendance $attendance) => $service->syncAttendance($attendance));
|
||||
}
|
||||
|
||||
private function dailyReport($userId, $userName)
|
||||
{
|
||||
$tasks = Tasks::with(['duration'])
|
||||
|
||||
@@ -30,6 +30,7 @@ class User extends Authenticatable
|
||||
'name',
|
||||
'number',
|
||||
'birthday',
|
||||
'work_start_time',
|
||||
'password',
|
||||
'user_name',
|
||||
'is_admin',
|
||||
|
||||
@@ -19,6 +19,34 @@ class LatePenaltyService
|
||||
public const WEEKLY_ALLOWANCE_MINUTES = 15;
|
||||
public const FINE_PER_MINUTE = 2000;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $workStartCache = [];
|
||||
|
||||
/** ساعت شروع کار کاربر (H:i)؛ اگر ادمین تعیین نکرده باشد ۰۸:۳۰ */
|
||||
public function workStartFor(?string $userIdentity): string
|
||||
{
|
||||
if (!$userIdentity) {
|
||||
return self::WORK_START;
|
||||
}
|
||||
|
||||
if (!array_key_exists($userIdentity, $this->workStartCache)) {
|
||||
$value = null;
|
||||
try {
|
||||
$value = User::where('user_name', $userIdentity)->value('work_start_time');
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
$this->workStartCache[$userIdentity] = $value ? substr((string) $value, 0, 5) : self::WORK_START;
|
||||
}
|
||||
|
||||
return $this->workStartCache[$userIdentity];
|
||||
}
|
||||
|
||||
public function forgetWorkStart(string $userIdentity): void
|
||||
{
|
||||
unset($this->workStartCache[$userIdentity]);
|
||||
}
|
||||
|
||||
/** جمعه و تعطیلات رسمی روز کاری نیستند؛ ورود در این روزها اضافهکاری است. */
|
||||
public function isWorkDay(Carbon $date): bool
|
||||
{
|
||||
@@ -59,7 +87,7 @@ class LatePenaltyService
|
||||
];
|
||||
}
|
||||
|
||||
$threshold = $entered->copy()->setTimeFromTimeString(self::WORK_START)->startOfMinute();
|
||||
$threshold = $entered->copy()->setTimeFromTimeString($this->workStartFor($userIdentity))->startOfMinute();
|
||||
|
||||
if ($userIdentity) {
|
||||
$leaveThreshold = $this->approvedLeaveWorkThreshold($userIdentity, $entered);
|
||||
@@ -104,7 +132,7 @@ class LatePenaltyService
|
||||
public function approvedLeaveWorkThreshold(string $userIdentity, Carbon $entered): Carbon|false|null
|
||||
{
|
||||
$day = $entered->toDateString();
|
||||
$workStart = $entered->copy()->setTimeFromTimeString(self::WORK_START)->startOfMinute();
|
||||
$workStart = $entered->copy()->setTimeFromTimeString($this->workStartFor($userIdentity))->startOfMinute();
|
||||
|
||||
$leaves = LeaveRequest::query()
|
||||
->where('user_identity', $userIdentity)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->time('work_start_time')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('work_start_time');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -198,6 +198,9 @@ $usersChunked = $users->chunk(3);
|
||||
<p>کارکرد ماه جاری: {{ $u->totaltime }}</p>
|
||||
<p>جریمه تأخیر ماه: {{ number_format($u->monthly_late_fine ?? 0) }} تومان</p>
|
||||
<p>تعداد درخواست ها: {{ $u->req_counts }}</p>
|
||||
@if($u->work_start_time)
|
||||
<p>شروع کار: {{ substr((string) $u->work_start_time, 0, 5) }}</p>
|
||||
@endif
|
||||
</a>
|
||||
@if($userType == 'admin')
|
||||
<button type="button"
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
{{-- align-center text-sm text-start">--}}
|
||||
{{-- <div class="grid grid-cols-2 h-64 gap-2 justify-items-stretch content-between text-sm indent-2.5 align-middle divide">--}}
|
||||
@csrf
|
||||
@if(!empty($viewerIsAdmin))
|
||||
<input type="hidden" name="target_username" value="{{ $user->user_name }}">
|
||||
@endif
|
||||
|
||||
<label class="pb-3">نام و نام خانوادگی:</label>
|
||||
<input type="text" name="name" value="{{ $user->name ?? '' }}"
|
||||
@@ -71,6 +74,18 @@
|
||||
<option value="1" {{ $user->is_admin == 1 ? 'selected' : '' }}>ادمین</option>
|
||||
</select>
|
||||
@endif
|
||||
|
||||
@if(!empty($viewerIsAdmin))
|
||||
<label class="pb-3">ساعت شروع کار:</label>
|
||||
<div class="flex flex-col gap-1">
|
||||
<input type="time" name="work_start_time"
|
||||
value="{{ $user->work_start_time ? substr((string) $user->work_start_time, 0, 5) : '' }}"
|
||||
class="border border-gray-300 rounded-md text-center">
|
||||
<span class="text-[11px] text-gray-500 indent-0">
|
||||
خالی = پیشفرض {{ \App\Services\LatePenaltyService::WORK_START }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
<button type="submit"
|
||||
class="col-span-2 bg-cyan-500 hover:bg-cyan-700 cursor-pointer
|
||||
rounded-xl border border-400 justify-self-center
|
||||
|
||||
Reference in New Issue
Block a user