Files
a.montask/app/Services/LatePenaltyService.php
T

246 lines
8.5 KiB
PHP

<?php
namespace App\Services;
use App\Models\Attendance;
use App\Models\LatePeriodFine;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Morilog\Jalali\Jalalian;
class LatePenaltyService
{
public const WORK_START = '08:30';
public const DAILY_GRACE_MINUTES = 15;
public const WEEKLY_ALLOWANCE_MINUTES = 15;
public const FINE_PER_MINUTE = 2000;
public function calculateFromEnteredAt(?string $enteredAt): array
{
if (!$enteredAt) {
return [
'late_minutes' => 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
{
$j = Jalalian::fromCarbon($date ?? Carbon::now());
$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;
}
}