add notifications logic and add support of both JWT and Sanctum

This commit is contained in:
root
2026-03-11 01:20:31 -04:00
parent f6be51576c
commit 182036cc41
141 changed files with 8685 additions and 648 deletions
@@ -0,0 +1,26 @@
<?php
namespace App\Console\Commands;
use App\Services\Attendance\AttendanceAutoPublishJobService;
use Illuminate\Console\Command;
class AttendanceAutoPublishCommand extends Command
{
protected $signature = 'attendance:auto-publish';
protected $description = 'Auto-publish attendance days per "second Sunday backward" rule.';
public function handle(AttendanceAutoPublishJobService $service): int
{
$result = $service->run();
$this->info(sprintf(
'Auto-publish checked at %s (TZ: %s). Published rows: %d.',
$result['checked_at'],
$result['timezone'],
$result['published']
));
return self::SUCCESS;
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Console\Commands;
use App\Services\Payments\PaymentMissedCheckService;
use Illuminate\Console\Command;
class CheckMissedPaymentsCommand extends Command
{
protected $signature = 'payments:check-missed';
protected $description = 'Checks for users who missed payments and sends reminders.';
public function handle(PaymentMissedCheckService $service): int
{
$users = $service->findUsersWithMissedPayments();
if (empty($users)) {
$this->info('No missed payments found.');
return self::SUCCESS;
}
$result = $service->sendReminders($users);
$this->info(sprintf('Finished checking missed payments. Sent: %d, Failed: %d.', $result['sent'], $result['failed']));
return self::SUCCESS;
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Console\Commands;
use App\Services\Notifications\NotificationCleanupService;
use Illuminate\Console\Command;
class CleanupExpiredNotificationsCommand extends Command
{
protected $signature = 'notifications:cleanup';
protected $description = 'Deletes expired notifications from the database.';
public function handle(NotificationCleanupService $service): int
{
$count = $service->cleanupExpired();
if ($count <= 0) {
$this->info('No expired notifications found to soft delete.');
return self::SUCCESS;
}
$this->info("Soft-deleted {$count} expired notifications.");
return self::SUCCESS;
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Console\Commands;
use App\Services\Auth\PasswordResetCleanupService;
use Illuminate\Console\Command;
class CleanupPasswordResetsCommand extends Command
{
protected $signature = 'cleanup:password-resets {--days=30}';
protected $description = 'Delete password reset requests older than N days.';
public function handle(PasswordResetCleanupService $service): int
{
$days = (int) $this->option('days');
$count = $service->cleanup($days > 0 ? $days : 30);
$this->info("Deleted {$count} old password reset request(s).");
return self::SUCCESS;
}
}
@@ -0,0 +1,37 @@
<?php
namespace App\Console\Commands;
use App\Services\System\ConfigUpdateService;
use DateTimeZone;
use Illuminate\Console\Command;
class ConfigUpdateCommand extends Command
{
protected $signature = 'config:update {task?} {--task=} {--dry} {--force} {--tz=}';
protected $description = 'Run a configuration update task (weekly cron).';
public function handle(ConfigUpdateService $service): int
{
$task = (string) ($this->option('task') ?: $this->argument('task') ?: '');
$dry = (bool) $this->option('dry');
$force = (bool) $this->option('force');
$tzName = (string) ($this->option('tz') ?: (config('School')->attendance['timezone'] ?? 'UTC'));
$tz = new DateTimeZone($tzName);
if ($task === '' || !in_array($task, $service->availableTasks(), true)) {
$this->error('Invalid or missing --task. Available: ' . implode(', ', $service->availableTasks()));
return self::FAILURE;
}
$result = $service->runTask($task, $dry, $force, $tz);
if (!$result['ok']) {
$this->error($result['message'] ?? "Task '{$task}' failed.");
return self::FAILURE;
}
$this->info("Task '{$task}' finished successfully." . ($dry ? ' [DRY RUN]' : ''));
return self::SUCCESS;
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Console\Commands;
use App\Services\Users\InactiveUserCleanupService;
use Illuminate\Console\Command;
class DeleteInactiveUsersCommand extends Command
{
protected $signature = 'users:delete-inactive-users {--minutes=15}';
protected $description = 'Delete users that are inactive and created more than N minutes ago.';
public function handle(InactiveUserCleanupService $service): int
{
$minutes = (int) $this->option('minutes');
$result = $service->cleanup($minutes > 0 ? $minutes : 15);
$this->info(sprintf(
'Deleted %d inactive users, %d parents rows, %d orphaned user_roles.',
$result['deleted_users'],
$result['deleted_parents'],
$result['deleted_roles']
));
return self::SUCCESS;
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Console\Commands;
use App\Services\Attendance\AttendanceSummaryRebuildService;
use Illuminate\Console\Command;
class RecalculateAttendanceCommand extends Command
{
protected $signature = 'attendance:recalculate-summary';
protected $description = 'Recalculates the attendance summary records from raw attendance data.';
public function handle(AttendanceSummaryRebuildService $service): int
{
$result = $service->rebuild();
$this->info('Attendance summary recalculation finished. Inserted: ' . $result['inserted']);
return self::SUCCESS;
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Console\Commands;
use App\Services\Attendance\AttendanceDailySummaryService;
use Illuminate\Console\Command;
class SendAbsenteesSummaryCommand extends Command
{
protected $signature = 'attendance:absentees-summary';
protected $description = 'Sends daily attendance summaries for absences to parents.';
public function handle(AttendanceDailySummaryService $service): int
{
$sent = $service->sendAbsenteesSummary();
$this->info("Attendance absentees summary completed. Sent: {$sent}.");
return self::SUCCESS;
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Console\Commands;
use App\Services\Attendance\AttendanceDailySummaryService;
use Illuminate\Console\Command;
class SendLatesSummaryCommand extends Command
{
protected $signature = 'attendance:lates-summary';
protected $description = 'Sends daily attendance summaries for lates to parents.';
public function handle(AttendanceDailySummaryService $service): int
{
$sent = $service->sendLatesSummary();
$this->info("Attendance lates summary completed. Sent: {$sent}.");
return self::SUCCESS;
}
}
@@ -0,0 +1,56 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Services\Payments\PaymentNotificationService;
use Illuminate\Console\Command;
class SendMonthlyPaymentNotificationsCommand extends Command
{
protected $signature = 'payments:monthly-reminder {--force} {--email=} {--type=}';
protected $description = 'Send monthly payment reminders on the first Saturday of every month.';
public function handle(PaymentNotificationService $service): int
{
$force = (bool) $this->option('force');
$email = (string) ($this->option('email') ?? '');
$type = (string) ($this->option('type') ?? '');
if ($email !== '') {
$user = User::query()->where('email', $email)->first();
if (!$user) {
$this->error('Invalid --email provided.');
return self::FAILURE;
}
$result = $service->send([
'parent_id' => (int) $user->id,
'type' => $type,
'force' => true,
]);
$this->info('Test reminder sent for ' . $email . '.');
return $result['failed'] > 0 ? self::FAILURE : self::SUCCESS;
}
if (!$force && !$this->isFirstSaturday(now())) {
$this->info('Not the first Saturday of the month. Use --force to override.');
return self::SUCCESS;
}
$result = $service->send([
'type' => $type,
'force' => $force,
]);
$this->info(sprintf('Done. Sent: %d, Skipped: %d, Failed: %d.', $result['sent'], $result['skipped'], $result['failed']));
return $result['failed'] > 0 ? self::FAILURE : self::SUCCESS;
}
private function isFirstSaturday(\DateTimeInterface $dt): bool
{
return (int) $dt->format('w') === 6 && (int) $dt->format('j') <= 7;
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Services\Payments\PaymentTestNotificationService;
use Illuminate\Console\Command;
class SendTestPaymentNotificationCommand extends Command
{
protected $signature = 'payments:send-test {--email=} {--type=no_payment}';
protected $description = 'Send a single test non-payment or installment reminder to a specific email.';
public function handle(PaymentTestNotificationService $service): int
{
$email = (string) $this->option('email');
$type = (string) $this->option('type');
if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->error('Usage: php artisan payments:send-test --email=addr@example.com [--type=no_payment|installment]');
return self::FAILURE;
}
$result = $service->send($email, $type);
if (!$result['ok']) {
$this->error('Failed to send test reminder to ' . $email . '.');
return self::FAILURE;
}
$this->info('Sent test reminder to ' . $email . '.');
return self::SUCCESS;
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Console\Commands;
use App\Services\Payments\PaypalPaymentSyncService;
use Illuminate\Console\Command;
class SyncPaypalPaymentsCommand extends Command
{
protected $signature = 'payments:sync-paypal {--dry-run} {--report-only}';
protected $description = 'Sync PayPal payments to internal payments table from paypal_payments.';
public function handle(PaypalPaymentSyncService $service): int
{
$dryRun = (bool) $this->option('dry-run');
$reportOnly = (bool) $this->option('report-only');
$result = $service->sync($dryRun, $reportOnly);
$this->info(sprintf('[%s] %d PayPal payments processed.', $result['mode'], $result['processed']));
if (!empty($result['failed'])) {
$this->error(sprintf('[%s] Failed transactions: %s', $result['mode'], implode(', ', $result['failed'])));
}
return self::SUCCESS;
}
}