0, 'grace_late_minutes' => 0, 'daily_fine_minutes' => 0, 'daily_fine_amount' => 0, ]; } $entered = Carbon::parse($enteredAt); $threshold = $entered->copy()->setTimeFromTimeString(self::WORK_START); if ($entered->lte($threshold)) { return [ 'late_minutes' => 0, 'grace_late_minutes' => 0, 'daily_fine_minutes' => 0, 'daily_fine_amount' => 0, ]; } $lateMinutes = (int) ceil($threshold->diffInSeconds($entered) / 60); // تأخیر داخل بازه ارفاق روزانه (۸:۳۰ تا ۸:۴۵) — مبنای جریمه هفتگی $graceLateMinutes = min($lateMinutes, self::DAILY_GRACE_MINUTES); $dailyFineMinutes = max(0, $lateMinutes - self::DAILY_GRACE_MINUTES); return [ 'late_minutes' => $lateMinutes, 'grace_late_minutes' => $graceLateMinutes, 'daily_fine_minutes' => $dailyFineMinutes, 'daily_fine_amount' => $dailyFineMinutes * self::FINE_PER_MINUTE, ]; } /** دقایق تأخیر داخل بازه ۸:۳۰–۸:۴۵ برای یک رکورد */ public function graceLateMinutes(Attendance $att): int { $late = (int) ($att->late_minutes ?? 0); if ($late <= 0) { return 0; } return min($late, self::DAILY_GRACE_MINUTES); } public function syncAttendance(Attendance $attendance): Attendance { if (!$attendance->entered_at) { $attendance->late_minutes = 0; $attendance->daily_fine_minutes = 0; $attendance->daily_fine_amount = 0; $attendance->save(); return $attendance; } // فقط اولین ورود هر روز برای جریمه تأخیر محاسبه می‌شود $day = Carbon::parse($attendance->entered_at)->toDateString(); $firstOfDay = Attendance::query() ->where('user_identity', $attendance->user_identity) ->whereDate('entered_at', $day) ->orderBy('entered_at') ->orderBy('id') ->first(); if ($firstOfDay && (int) $firstOfDay->id !== (int) $attendance->id) { $attendance->late_minutes = 0; $attendance->daily_fine_minutes = 0; $attendance->daily_fine_amount = 0; $attendance->save(); return $attendance; } $calc = $this->calculateFromEnteredAt($attendance->entered_at); $attendance->late_minutes = $calc['late_minutes']; $attendance->daily_fine_minutes = $calc['daily_fine_minutes']; $attendance->daily_fine_amount = $calc['daily_fine_amount']; $attendance->save(); // اگر این رکورد اولین ورود شد، بقیه همان روز را صفر کن Attendance::query() ->where('user_identity', $attendance->user_identity) ->whereDate('entered_at', $day) ->where('id', '!=', $attendance->id) ->update([ 'late_minutes' => 0, 'daily_fine_minutes' => 0, 'daily_fine_amount' => 0, ]); return $attendance; } public function weekBounds(?Carbon $date = null): array { $date = ($date ?? Carbon::now())->copy()->startOfDay(); // هفته ایرانی: شنبه تا جمعه $dayOfWeek = (int) $date->dayOfWeek; // 0=Sun ... 6=Sat $daysFromSaturday = ($dayOfWeek + 1) % 7; $start = $date->copy()->subDays($daysFromSaturday)->startOfDay(); $end = $start->copy()->addDays(6)->endOfDay(); return [$start, $end]; } public function jalaliMonthBounds(?Carbon $date = null): array { $carbon = $date ?? Carbon::now(); if ($carbon->year < 1000 || $carbon->year > 3000) { $carbon = Carbon::now(); } $j = Jalalian::fromCarbon($carbon); $start = (new Jalalian($j->getYear(), $j->getMonth(), 1))->toCarbon()->startOfDay(); $endDay = $j->getMonth() <= 6 ? 31 : ($j->getMonth() <= 11 ? 30 : ($j->isLeapYear() ? 30 : 29)); $end = (new Jalalian($j->getYear(), $j->getMonth(), $endDay))->toCarbon()->endOfDay(); return [$start, $end]; } public function firstEntriesBetween(Carbon $start, Carbon $end, ?string $userIdentity = null): Collection { $query = Attendance::query() ->whereNotNull('entered_at') ->whereBetween('entered_at', [$start, $end]) ->orderBy('entered_at') ->orderBy('id'); if ($userIdentity) { $query->where('user_identity', $userIdentity); } return $query->get() ->groupBy(fn (Attendance $att) => $att->user_identity . '|' . Carbon::parse($att->entered_at)->toDateString()) ->map(fn (Collection $items) => $items->first()) ->values(); } public function summarizeUserPeriod(string $userIdentity, Carbon $start, Carbon $end): array { $entries = $this->firstEntriesBetween($start, $end, $userIdentity); $lateMinutes = (int) $entries->sum(fn (Attendance $att) => (int) ($att->late_minutes ?? 0)); $graceLateMinutes = (int) $entries->sum(fn (Attendance $att) => $this->graceLateMinutes($att)); $dailyFineAmount = (int) $entries->sum(fn (Attendance $att) => (int) ($att->daily_fine_amount ?? 0)); return [ 'late_minutes' => $lateMinutes, 'grace_late_minutes' => $graceLateMinutes, 'daily_fine_amount' => $dailyFineAmount, 'entries' => $entries, ]; } /** * جریمه هفتگی = (جمع تأخیرهای ۸:۳۰–۸:۴۵ − ۱۵ دقیقه ارفاق) × ۲۰۰۰ */ public function computeWeeklyFine(int $graceLateMinutes): array { $fineMinutes = max(0, $graceLateMinutes - self::WEEKLY_ALLOWANCE_MINUTES); return [ 'allowance_minutes' => self::WEEKLY_ALLOWANCE_MINUTES, 'fine_minutes' => $fineMinutes, 'fine_amount' => $fineMinutes * self::FINE_PER_MINUTE, ]; } public function formatAmount(int $amount): string { return number_format($amount) . ' تومان'; } public function sendToAdmins(string $message): void { $admins = User::query() ->whereNotNull('is_admin') ->whereNotNull('bale') ->select('bale') ->get(); foreach ($admins as $admin) { try { Http::withoutVerifying()->post( 'https://tapi.bale.ai/1482838710:sYbLzby0tuLRzHvP4d3LsbcNwg2sjSDLTAU/sendMessage', [ 'chat_id' => $admin->bale, 'text' => $message, ] ); } catch (\Exception $e) { } } } public function userName(string $userIdentity): string { return User::where('user_name', $userIdentity)->value('name') ?: $userIdentity; } public function storePeriodFine(array $data): LatePeriodFine { return LatePeriodFine::updateOrCreate( [ 'user_identity' => $data['user_identity'], 'period_type' => $data['period_type'], 'period_start' => $data['period_start'], 'period_end' => $data['period_end'], ], $data ); } public function monthlyTotalFine(string $userIdentity, Carbon $monthStart, Carbon $monthEnd): int { $daily = (int) Attendance::query() ->where('user_identity', $userIdentity) ->whereBetween('entered_at', [$monthStart, $monthEnd]) ->sum('daily_fine_amount'); $weekly = (int) LatePeriodFine::query() ->where('user_identity', $userIdentity) ->where('period_type', 'weekly') ->whereBetween('period_start', [$monthStart->toDateString(), $monthEnd->toDateString()]) ->sum('fine_amount'); return $daily + $weekly; } }