246 lines
8.5 KiB
PHP
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;
|
|
}
|
|
}
|