2026-07-26 10:49:48 +03:30
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
|
|
use App\Models\Attendance;
|
|
|
|
|
use App\Models\LeaveRequest;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Services\LatePenaltyService;
|
|
|
|
|
use App\Support\JalaliHelper;
|
|
|
|
|
use Carbon\Carbon;
|
|
|
|
|
use Illuminate\Http\Request;
|
2026-07-26 11:02:22 +03:30
|
|
|
use Illuminate\Support\Facades\Http;
|
2026-07-26 10:49:48 +03:30
|
|
|
use Morilog\Jalali\Jalalian;
|
|
|
|
|
|
|
|
|
|
class LeaveController extends Controller
|
|
|
|
|
{
|
|
|
|
|
public function store(Request $request)
|
|
|
|
|
{
|
|
|
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
|
|
|
return redirect('/')->with('message', [
|
|
|
|
|
'color' => '#ff8800ff',
|
|
|
|
|
'text' => 'ابتدا باید وارد شوید!',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
|
|
|
if (!$user) {
|
|
|
|
|
return redirect('/')->with('message', [
|
|
|
|
|
'color' => '#ff8800ff',
|
|
|
|
|
'text' => 'کاربر یافت نشد!',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$type = $request->input('type');
|
|
|
|
|
if (!in_array($type, ['daily', 'hourly'], true)) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff4400ff',
|
|
|
|
|
'text' => 'نوع مرخصی نامعتبر است.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($type === 'daily') {
|
|
|
|
|
$dateFrom = $this->parseJalaliDate($request->input('date_from'));
|
|
|
|
|
$dateTo = $this->parseJalaliDate($request->input('date_to'));
|
|
|
|
|
|
|
|
|
|
if (!$dateFrom || !$dateTo) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff4400ff',
|
|
|
|
|
'text' => 'بازه تاریخ مرخصی روزانه الزامی است.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if ($dateTo < $dateFrom) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff4400ff',
|
|
|
|
|
'text' => 'تاریخ پایان نمیتواند قبل از شروع باشد.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
$timeFrom = null;
|
|
|
|
|
$timeTo = null;
|
|
|
|
|
} else {
|
|
|
|
|
$dateFrom = $this->parseJalaliDate($request->input('date'));
|
|
|
|
|
$dateTo = $dateFrom;
|
|
|
|
|
$timeFrom = $this->normalizeTime($request->input('time_from'));
|
|
|
|
|
$timeTo = $this->normalizeTime($request->input('time_to'));
|
|
|
|
|
|
|
|
|
|
if (!$dateFrom || !$timeFrom || !$timeTo) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff4400ff',
|
|
|
|
|
'text' => 'تاریخ و ساعت شروع/پایان مرخصی ساعتی الزامی است.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if ($timeTo <= $timeFrom) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff4400ff',
|
|
|
|
|
'text' => 'ساعت پایان باید بعد از ساعت شروع باشد.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$leave = LeaveRequest::create([
|
|
|
|
|
'user_id' => $user->id,
|
|
|
|
|
'user_identity' => $user->user_name,
|
|
|
|
|
'type' => $type,
|
|
|
|
|
'date_from' => $dateFrom,
|
|
|
|
|
'date_to' => $dateTo,
|
|
|
|
|
'time_from' => $timeFrom,
|
|
|
|
|
'time_to' => $timeTo,
|
|
|
|
|
'reason' => $request->input('reason'),
|
|
|
|
|
'status' => 'pending',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$user->req_counts = (int) $user->req_counts + 1;
|
|
|
|
|
$user->save();
|
|
|
|
|
|
|
|
|
|
$this->notifyAdminsNewLeave($leave, $user);
|
|
|
|
|
|
|
|
|
|
return redirect('/')->with('message', [
|
|
|
|
|
'color' => '#00c300ff',
|
|
|
|
|
'text' => 'درخواست مرخصی ثبت شد و برای تأیید ادمین ارسال گردید.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function decide(Request $request)
|
|
|
|
|
{
|
|
|
|
|
if (!isset($_COOKIE['user_identity'])) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff8800ff',
|
|
|
|
|
'text' => 'ابتدا باید وارد شوید!',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$admin = User::where('user_name', $_COOKIE['user_identity'])->first();
|
|
|
|
|
if (!$admin || !in_array((string) $admin->is_admin, ['1', '2'], true)) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff8800ff',
|
|
|
|
|
'text' => 'دسترسی ندارید!',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$leave = LeaveRequest::find($request->input('leave_id'));
|
|
|
|
|
if (!$leave || $leave->status !== 'pending') {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff8800ff',
|
|
|
|
|
'text' => 'درخواست مرخصی یافت نشد.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$action = $request->input('update');
|
|
|
|
|
if (!in_array($action, ['approve', 'reject'], true)) {
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#ff4400ff',
|
|
|
|
|
'text' => 'عملیات نامعتبر است.',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$leave->status = $action === 'approve' ? 'approved' : 'rejected';
|
|
|
|
|
$leave->reviewed_by = $admin->id;
|
|
|
|
|
$leave->reviewed_at = now();
|
|
|
|
|
$leave->save();
|
|
|
|
|
|
|
|
|
|
$owner = User::where('user_name', $leave->user_identity)->first();
|
|
|
|
|
if ($owner && (int) $owner->req_counts > 0) {
|
|
|
|
|
$owner->req_counts -= 1;
|
|
|
|
|
$owner->save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($leave->status === 'approved') {
|
|
|
|
|
$this->recalculateLateForLeavePeriod($leave);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$statusFa = $leave->status === 'approved' ? 'تایید' : 'رد';
|
|
|
|
|
if ($owner) {
|
2026-07-26 11:02:22 +03:30
|
|
|
$this->notifyUserLeaveDecision($owner, $statusFa);
|
2026-07-26 10:49:48 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return back()->with('message', [
|
|
|
|
|
'color' => '#6aff00ff',
|
|
|
|
|
'text' => "درخواست مرخصی با موفقیت {$statusFa} شد.",
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-26 11:02:22 +03:30
|
|
|
private function notifyUserLeaveDecision(User $user, string $statusFa): void
|
|
|
|
|
{
|
|
|
|
|
if (!$user->bale) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
Http::withoutVerifying()->post(
|
|
|
|
|
'https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage',
|
|
|
|
|
[
|
|
|
|
|
'chat_id' => $user->bale,
|
|
|
|
|
'text' => "درخواست مرخصی شما {$statusFa} شد.",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-26 10:49:48 +03:30
|
|
|
private function recalculateLateForLeavePeriod(LeaveRequest $leave): void
|
|
|
|
|
{
|
|
|
|
|
$service = app(LatePenaltyService::class);
|
|
|
|
|
$from = Carbon::parse($leave->date_from)->startOfDay();
|
|
|
|
|
$to = Carbon::parse($leave->date_to)->endOfDay();
|
|
|
|
|
|
|
|
|
|
$attendances = Attendance::query()
|
|
|
|
|
->where('user_identity', $leave->user_identity)
|
|
|
|
|
->whereBetween('entered_at', [$from, $to])
|
|
|
|
|
->orderBy('entered_at')
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
foreach ($attendances as $attendance) {
|
|
|
|
|
$service->syncAttendance($attendance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function notifyAdminsNewLeave(LeaveRequest $leave, User $user): void
|
|
|
|
|
{
|
|
|
|
|
$typeFa = $leave->type === 'daily' ? 'روزانه' : 'ساعتی';
|
|
|
|
|
$from = JalaliHelper::format($leave->date_from, 'Y/m/d');
|
|
|
|
|
$to = JalaliHelper::format($leave->date_to, 'Y/m/d');
|
|
|
|
|
|
|
|
|
|
$message = "درخواست مرخصی {$typeFa}\n";
|
|
|
|
|
$message .= "کارمند: {$user->name}\n";
|
|
|
|
|
if ($leave->type === 'daily') {
|
|
|
|
|
$message .= "از {$from} تا {$to}\n";
|
|
|
|
|
} else {
|
|
|
|
|
$timeFrom = substr((string) $leave->time_from, 0, 5);
|
|
|
|
|
$timeTo = substr((string) $leave->time_to, 0, 5);
|
|
|
|
|
$message .= "تاریخ: {$from}\nساعت: {$timeFrom} تا {$timeTo}\n";
|
|
|
|
|
}
|
|
|
|
|
if ($leave->reason) {
|
|
|
|
|
$message .= "دلیل: {$leave->reason}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->send_admins($message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function parseJalaliDate(?string $value): ?string
|
|
|
|
|
{
|
|
|
|
|
if (!$value) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$value = \Morilog\Jalali\CalendarUtils::convertNumbers(trim($value), true);
|
|
|
|
|
$value = str_replace('/', '-', $value);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
|
|
|
|
// اگر سال شمسی باشد (مثلاً 1404) با Jalalian تبدیل میشود
|
|
|
|
|
$year = (int) substr($value, 0, 4);
|
|
|
|
|
if ($year >= 1300 && $year <= 1500) {
|
|
|
|
|
return Jalalian::fromFormat('Y-m-d', $value)->toCarbon()->format('Y-m-d');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Carbon::parse($value)->format('Y-m-d');
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function normalizeTime(?string $value): ?string
|
|
|
|
|
{
|
|
|
|
|
if (!$value) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
$value = trim(\Morilog\Jalali\CalendarUtils::convertNumbers($value, true));
|
|
|
|
|
if (preg_match('/^(\d{1,2}):(\d{2})/', $value, $m)) {
|
|
|
|
|
return sprintf('%02d:%02d:00', (int) $m[1], (int) $m[2]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|