Adding delay and penalty calculations

This commit is contained in:
2026-07-22 14:10:05 +03:30
parent 28bf82cde9
commit e1f86e6ffe
21 changed files with 1285 additions and 99 deletions
@@ -0,0 +1,75 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Morilog\Jalali\Jalalian;
class NotifyUpcomingBirthdays extends Command
{
protected $signature = 'birthdays:notify';
protected $description = 'Send Bale reminder to admins one day before employee birthdays';
public function handle(): int
{
$tomorrow = Jalalian::now()->addDays(1);
$month = $tomorrow->getMonth();
$day = $tomorrow->getDay();
$jalaliDate = $tomorrow->format('j F');
$users = User::query()
->whereNotNull('birthday')
->whereNull('deleted_at')
->get()
->filter(function (User $user) use ($month, $day) {
$birth = Jalalian::fromCarbon(Carbon::parse($user->birthday));
return $birth->getMonth() === $month && $birth->getDay() === $day;
});
if ($users->isEmpty()) {
$this->info('No birthdays tomorrow.');
return self::SUCCESS;
}
foreach ($users as $user) {
$name = $user->name ?: $user->user_name;
$message = "🌸 یک یادآوری لطیف از قلب تیم...\n\n"
. "فردا {$jalaliDate}، روز تولد «{$name}» است.\n\n"
. "فردایی که می‌تواند با یک پیام گرم، یک لبخند، یا یک «تولدت مبارک» ساده، برایش خاطره‌ای قشنگ بسازد.\n"
. "بیایید این روز را از یاد نبریم و بهش حس ارزشمند بودن بدهیم 💛";
$this->sendToAdmins($message);
$this->info("Birthday reminder sent for: {$name}");
}
return self::SUCCESS;
}
private 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) {
}
}
}
}
@@ -0,0 +1,64 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Services\LatePenaltyService;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Morilog\Jalali\Jalalian;
class SendDailyLateReport extends Command
{
protected $signature = 'late:daily-report {--date=}';
protected $description = 'Send daily late-penalty report to admins via Bale';
public function handle(LatePenaltyService $service): int
{
$date = $this->option('date')
? Carbon::parse($this->option('date'))->startOfDay()
: Carbon::yesterday()->startOfDay();
$start = $date->copy()->startOfDay();
$end = $date->copy()->endOfDay();
$jalali = Jalalian::fromCarbon($date)->format('Y/m/d');
$entries = $service->firstEntriesBetween($start, $end)
->filter(fn ($att) => (int) ($att->late_minutes ?? 0) > 0 || (int) ($att->daily_fine_amount ?? 0) > 0)
->values();
if ($entries->isEmpty()) {
$message = "📋 گزارش تأخیر روزانه\nتاریخ: {$jalali}\n\nامروز تأخیر مشمول جریمه ثبت نشده است.";
$service->sendToAdmins($message);
$this->info('Daily late report sent (empty).');
return self::SUCCESS;
}
$lines = ["📋 گزارش تأخیر روزانه", "تاریخ: {$jalali}", ""];
$totalFine = 0;
foreach ($entries as $att) {
$name = $service->userName($att->user_identity);
$enter = Carbon::parse($att->entered_at)->format('H:i');
$late = (int) $att->late_minutes;
$fineMin = (int) $att->daily_fine_minutes;
$fine = (int) $att->daily_fine_amount;
$totalFine += $fine;
$lines[] = "• {$name}";
$lines[] = " ورود: {$enter} | تأخیر: {$late} دقیقه";
$lines[] = $fineMin > 0
? " جریمه روزانه: {$fineMin} دقیقه × " . number_format(LatePenaltyService::FINE_PER_MINUTE) . " = " . $service->formatAmount($fine)
: " جریمه روزانه: ۰ (داخل ارفاق ۱۵ دقیقه‌ای)";
$lines[] = "";
}
$lines[] = "جمع جریمه روز: " . $service->formatAmount($totalFine);
$service->sendToAdmins(implode("\n", $lines));
$this->info('Daily late report sent.');
return self::SUCCESS;
}
}
@@ -0,0 +1,107 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Services\LatePenaltyService;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Morilog\Jalali\Jalalian;
class SendMonthlyLateReport extends Command
{
protected $signature = 'late:monthly-report {--date=}';
protected $description = 'Send monthly late-penalty report to admins via Bale';
public function handle(LatePenaltyService $service): int
{
$forcedDate = $this->option('date');
// پیش‌فرض: ماه شمسیِ دیروز (برای اجرای روز اول ماه بعد)
$anchor = $forcedDate
? Carbon::parse($forcedDate)
: Carbon::yesterday();
// بدون --date فقط وقتی دیروز آخرین روز ماه شمسی بوده اجرا شود
if (!$forcedDate) {
$jYesterday = Jalalian::fromCarbon($anchor);
$daysInMonth = $jYesterday->getMonth() <= 6
? 31
: ($jYesterday->getMonth() <= 11 ? 30 : ($jYesterday->isLeapYear() ? 30 : 29));
if ($jYesterday->getDay() !== $daysInMonth) {
$this->info('Not end of Jalali month; skipped.');
return self::SUCCESS;
}
}
[$start, $end] = $service->jalaliMonthBounds($anchor);
$monthName = Jalalian::fromCarbon($start)->format('%B %Y');
$users = User::query()
->whereNotNull('user_name')
->whereNull('deleted_at')
->get(['user_name', 'name']);
$lines = [
"📅 گزارش تأخیر ماهانه",
"ماه: {$monthName}",
"",
];
$any = false;
$monthTotal = 0;
foreach ($users as $user) {
$summary = $service->summarizeUserPeriod($user->user_name, $start, $end);
$weeklyAmount = (int) \App\Models\LatePeriodFine::query()
->where('user_identity', $user->user_name)
->where('period_type', 'weekly')
->whereBetween('period_start', [$start->toDateString(), $end->toDateString()])
->sum('fine_amount');
$total = $summary['daily_fine_amount'] + $weeklyAmount;
if ($summary['late_minutes'] <= 0 && $total <= 0) {
continue;
}
$any = true;
$monthTotal += $total;
$service->storePeriodFine([
'user_identity' => $user->user_name,
'period_type' => 'monthly',
'period_start' => $start->toDateString(),
'period_end' => $end->toDateString(),
'late_minutes' => $summary['late_minutes'],
'allowance_minutes' => 0,
'fine_minutes' => 0,
'fine_amount' => $total,
'daily_fine_amount' => $summary['daily_fine_amount'],
'weekly_fine_amount' => $weeklyAmount,
'total_fine_amount' => $total,
]);
$name = $user->name ?: $user->user_name;
$lines[] = "• {$name}";
$lines[] = " مجموع تأخیر ماه: {$summary['late_minutes']} دقیقه";
$lines[] = " جریمه روزانه: " . $service->formatAmount($summary['daily_fine_amount']);
$lines[] = " جریمه هفتگی: " . $service->formatAmount($weeklyAmount);
$lines[] = " جمع کل: " . $service->formatAmount($total);
$lines[] = "";
}
if (!$any) {
$lines[] = "در این ماه تأخیر مشمول جریمه‌ای ثبت نشده است.";
} else {
$lines[] = "جمع کل جرایم ماه: " . $service->formatAmount($monthTotal);
}
$service->sendToAdmins(implode("\n", $lines));
$this->info('Monthly late report sent.');
return self::SUCCESS;
}
}
@@ -0,0 +1,98 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Services\LatePenaltyService;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Morilog\Jalali\Jalalian;
class SendWeeklyLateReport extends Command
{
protected $signature = 'late:weekly-report {--date=}';
protected $description = 'Send weekly late-penalty report to admins via Bale';
public function handle(LatePenaltyService $service): int
{
$anchor = $this->option('date')
? Carbon::parse($this->option('date'))
: Carbon::yesterday();
[$start, $end] = $service->weekBounds($anchor);
$startLabel = Jalalian::fromCarbon($start)->format('Y/m/d');
$endLabel = Jalalian::fromCarbon($end)->format('Y/m/d');
$users = User::query()
->whereNotNull('user_name')
->whereNull('deleted_at')
->get(['user_name', 'name']);
$lines = [
"📊 گزارش تأخیر هفتگی",
"بازه: {$startLabel} تا {$endLabel}",
"ارفاق هفتگی: " . LatePenaltyService::WEEKLY_ALLOWANCE_MINUTES . " دقیقه (از جمع تأخیرهای ۸:۳۰ تا ۸:۴۵)",
"",
];
$any = false;
$sumWeeklyFine = 0;
$sumDailyFine = 0;
$sumReportTotal = 0;
foreach ($users as $user) {
$summary = $service->summarizeUserPeriod($user->user_name, $start, $end);
if ($summary['grace_late_minutes'] <= 0 && $summary['daily_fine_amount'] <= 0) {
continue;
}
$any = true;
// فقط تأخیر بازه ۸:۳۰–۸:۴۵ برای جریمه هفتگی
$weekly = $service->computeWeeklyFine($summary['grace_late_minutes']);
$reportTotal = $weekly['fine_amount'] + $summary['daily_fine_amount'];
$sumWeeklyFine += $weekly['fine_amount'];
$sumDailyFine += $summary['daily_fine_amount'];
$sumReportTotal += $reportTotal;
$service->storePeriodFine([
'user_identity' => $user->user_name,
'period_type' => 'weekly',
'period_start' => $start->toDateString(),
'period_end' => $end->toDateString(),
'late_minutes' => $summary['grace_late_minutes'],
'allowance_minutes' => $weekly['allowance_minutes'],
'fine_minutes' => $weekly['fine_minutes'],
'fine_amount' => $weekly['fine_amount'],
'daily_fine_amount' => $summary['daily_fine_amount'],
'weekly_fine_amount' => $weekly['fine_amount'],
'total_fine_amount' => $reportTotal,
]);
$name = $user->name ?: $user->user_name;
$lines[] = "• {$name}";
$lines[] = " جمع تأخیر ۸:۳۰–۸:۴۵: {$summary['grace_late_minutes']} دقیقه";
$lines[] = " ارفاق هفتگی: {$weekly['allowance_minutes']} دقیقه";
$lines[] = $weekly['fine_minutes'] > 0
? " جریمه هفتگی: {$weekly['fine_minutes']} × " . number_format(LatePenaltyService::FINE_PER_MINUTE) . " = " . $service->formatAmount($weekly['fine_amount'])
: " جریمه هفتگی: ۰";
$lines[] = " جریمه روزانه این هفته: " . $service->formatAmount($summary['daily_fine_amount']);
$lines[] = " جمع گزارش: " . $service->formatAmount($reportTotal);
$lines[] = "";
}
if (!$any) {
$lines[] = "در این هفته تأخیری ثبت نشده است.";
} else {
$lines[] = "جمع جریمه هفتگی: " . $service->formatAmount($sumWeeklyFine);
$lines[] = "جمع جریمه روزانه: " . $service->formatAmount($sumDailyFine);
$lines[] = "جمع کل گزارش هفته: " . $service->formatAmount($sumReportTotal);
}
$service->sendToAdmins(implode("\n", $lines));
$this->info('Weekly late report sent.');
return self::SUCCESS;
}
}