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') {
|
if ($u->is_admin == '1' || $u->is_admin == '2') {
|
||||||
setcookie('ad', '1', time() + (60 * 60 * 24 * 365 * 5), "/");
|
setcookie('ad', '1', time() + (60 * 60 * 24 * 365 * 5), "/");
|
||||||
$users = User::whereNotNull('user_name')
|
$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();
|
->get();
|
||||||
|
|
||||||
$this->enrichUsersForDashboard($users);
|
$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)
|
public function profile(Request $request)
|
||||||
{
|
{
|
||||||
$username = $request->query('username') ?: $_COOKIE['user_identity'];
|
$username = $request->query('username') ?: $_COOKIE['user_identity'];
|
||||||
$user = User::where('user_name', $username)->first();
|
$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)
|
public function updateProfile(Request $request)
|
||||||
{
|
{
|
||||||
// dd($request->all());
|
$viewerIsAdmin = $this->viewerIsAdmin();
|
||||||
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
$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) {
|
if ($user->password != $request->password) {
|
||||||
$request->validate([
|
$request->validate([
|
||||||
@@ -645,14 +665,53 @@ class AttendanceController extends Controller
|
|||||||
$user->is_admin = $request->role == 1 ? 1 : null;
|
$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();
|
$user->save();
|
||||||
|
|
||||||
|
if ($workStartChanged) {
|
||||||
|
$this->resyncCurrentMonthLate($user->user_name);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('message', [
|
return redirect()->back()->with('message', [
|
||||||
'color' => '#00c300ff',
|
'color' => '#00c300ff',
|
||||||
'text' => 'اطلاعات با موفقیت ویرایش شد.'
|
'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)
|
private function dailyReport($userId, $userName)
|
||||||
{
|
{
|
||||||
$tasks = Tasks::with(['duration'])
|
$tasks = Tasks::with(['duration'])
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class User extends Authenticatable
|
|||||||
'name',
|
'name',
|
||||||
'number',
|
'number',
|
||||||
'birthday',
|
'birthday',
|
||||||
|
'work_start_time',
|
||||||
'password',
|
'password',
|
||||||
'user_name',
|
'user_name',
|
||||||
'is_admin',
|
'is_admin',
|
||||||
|
|||||||
@@ -19,6 +19,34 @@ class LatePenaltyService
|
|||||||
public const WEEKLY_ALLOWANCE_MINUTES = 15;
|
public const WEEKLY_ALLOWANCE_MINUTES = 15;
|
||||||
public const FINE_PER_MINUTE = 2000;
|
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
|
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) {
|
if ($userIdentity) {
|
||||||
$leaveThreshold = $this->approvedLeaveWorkThreshold($userIdentity, $entered);
|
$leaveThreshold = $this->approvedLeaveWorkThreshold($userIdentity, $entered);
|
||||||
@@ -104,7 +132,7 @@ class LatePenaltyService
|
|||||||
public function approvedLeaveWorkThreshold(string $userIdentity, Carbon $entered): Carbon|false|null
|
public function approvedLeaveWorkThreshold(string $userIdentity, Carbon $entered): Carbon|false|null
|
||||||
{
|
{
|
||||||
$day = $entered->toDateString();
|
$day = $entered->toDateString();
|
||||||
$workStart = $entered->copy()->setTimeFromTimeString(self::WORK_START)->startOfMinute();
|
$workStart = $entered->copy()->setTimeFromTimeString($this->workStartFor($userIdentity))->startOfMinute();
|
||||||
|
|
||||||
$leaves = LeaveRequest::query()
|
$leaves = LeaveRequest::query()
|
||||||
->where('user_identity', $userIdentity)
|
->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>کارکرد ماه جاری: {{ $u->totaltime }}</p>
|
||||||
<p>جریمه تأخیر ماه: {{ number_format($u->monthly_late_fine ?? 0) }} تومان</p>
|
<p>جریمه تأخیر ماه: {{ number_format($u->monthly_late_fine ?? 0) }} تومان</p>
|
||||||
<p>تعداد درخواست ها: {{ $u->req_counts }}</p>
|
<p>تعداد درخواست ها: {{ $u->req_counts }}</p>
|
||||||
|
@if($u->work_start_time)
|
||||||
|
<p>شروع کار: {{ substr((string) $u->work_start_time, 0, 5) }}</p>
|
||||||
|
@endif
|
||||||
</a>
|
</a>
|
||||||
@if($userType == 'admin')
|
@if($userType == 'admin')
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
{{-- align-center text-sm text-start">--}}
|
{{-- 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">--}}
|
{{-- <div class="grid grid-cols-2 h-64 gap-2 justify-items-stretch content-between text-sm indent-2.5 align-middle divide">--}}
|
||||||
@csrf
|
@csrf
|
||||||
|
@if(!empty($viewerIsAdmin))
|
||||||
|
<input type="hidden" name="target_username" value="{{ $user->user_name }}">
|
||||||
|
@endif
|
||||||
|
|
||||||
<label class="pb-3">نام و نام خانوادگی:</label>
|
<label class="pb-3">نام و نام خانوادگی:</label>
|
||||||
<input type="text" name="name" value="{{ $user->name ?? '' }}"
|
<input type="text" name="name" value="{{ $user->name ?? '' }}"
|
||||||
@@ -71,6 +74,18 @@
|
|||||||
<option value="1" {{ $user->is_admin == 1 ? 'selected' : '' }}>ادمین</option>
|
<option value="1" {{ $user->is_admin == 1 ? 'selected' : '' }}>ادمین</option>
|
||||||
</select>
|
</select>
|
||||||
@endif
|
@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"
|
<button type="submit"
|
||||||
class="col-span-2 bg-cyan-500 hover:bg-cyan-700 cursor-pointer
|
class="col-span-2 bg-cyan-500 hover:bg-cyan-700 cursor-pointer
|
||||||
rounded-xl border border-400 justify-self-center
|
rounded-xl border border-400 justify-self-center
|
||||||
|
|||||||
Reference in New Issue
Block a user