migrant-web/app/Console/Commands/SendReminders.php

475 lines
19 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Carbon\Carbon;
class SendReminders extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:send-reminders';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send automated reminder notifications for document expiry and pending confirmations';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Starting automated reminder notification system...');
$this->info('1. Checking Worker Documents...');
$this->checkWorkerDocuments();
$this->info('2. Checking Employer Profiles (Emirates ID Expiry)...');
$this->checkEmployerProfiles();
$this->info('3. Checking Sponsors (Emirates ID and License Expiry)...');
$this->checkSponsors();
$this->info('4. Checking Pending Hire Confirmations (Employer)...');
$this->checkHireConfirmations();
$this->info('5. Checking Pending Joining Confirmations (Worker)...');
$this->checkJoiningConfirmations();
$this->info('6. Checking Pending Direct Job Offers...');
$this->checkJobOffers();
$this->info('7. Checking Worker No Response...');
$this->checkWorkerNoResponse();
$this->info('8. Checking Review Reminders...');
$this->checkReviewReminders();
$this->info('Automated reminder system run complete!');
}
private function getReminderLevel(int $days, string $configKey, string $default): ?string
{
$value = config($configKey, $default);
$configuredDays = array_map('intval', array_filter(explode(',', $value), 'strlen'));
if (in_array($days, $configuredDays)) {
return $days === 1 ? '1_day' : "{$days}_days";
}
return null;
}
private function alreadySent($notifiable, string $eventType, $entity, string $reminderLevel): bool
{
return \App\Models\ReminderLog::where('user_type', get_class($notifiable))
->where('user_id', $notifiable->id)
->where('event_type', $eventType)
->where('entity_type', get_class($entity))
->where('entity_id', $entity->id)
->where('reminder_level', $reminderLevel)
->exists();
}
private function logReminder($notifiable, string $eventType, $entity, string $reminderLevel, string $channel): void
{
\App\Models\ReminderLog::create([
'user_type' => get_class($notifiable),
'user_id' => $notifiable->id,
'event_type' => $eventType,
'entity_type' => get_class($entity),
'entity_id' => $entity->id,
'reminder_level' => $reminderLevel,
'channel' => $channel,
'sent_at' => now(),
]);
}
private function checkWorkerDocuments()
{
$workerDocs = \App\Models\WorkerDocument::whereNotNull('expiry_date')->get();
foreach ($workerDocs as $doc) {
$worker = $doc->worker;
if (!$worker) continue;
$expiry = Carbon::parse($doc->expiry_date);
$days = (int) now()->startOfDay()->diffInDays($expiry->startOfDay(), false);
$level = $this->getReminderLevel($days, 'reminders.document_expiry_reminder_days', '30,7,3,1');
if (!$level) continue;
if ($this->alreadySent($worker, 'document_expiry', $doc, $level)) continue;
$this->info("Sending {$level} reminder to worker {$worker->name} for document {$doc->type} expiry.");
$worker->notify(new \App\Notifications\DocumentExpiryNotification(
ucfirst($doc->type),
$doc->expiry_date,
$days,
$level
));
$this->logReminder($worker, 'document_expiry', $doc, $level, 'all');
}
}
private function checkEmployerProfiles()
{
$profiles = \App\Models\EmployerProfile::whereNotNull('emirates_id_expiry')->get();
foreach ($profiles as $profile) {
$user = \App\Models\User::find($profile->user_id);
if (!$user) continue;
$expiry = Carbon::parse($profile->emirates_id_expiry);
$days = (int) now()->startOfDay()->diffInDays($expiry->startOfDay(), false);
$level = $this->getReminderLevel($days, 'reminders.document_expiry_reminder_days', '30,7,3,1');
if (!$level) continue;
if ($this->alreadySent($user, 'document_expiry', $profile, $level)) continue;
$this->info("Sending {$level} reminder to employer {$user->name} for Emirates ID expiry.");
$user->notify(new \App\Notifications\DocumentExpiryNotification(
'Emirates ID',
$profile->emirates_id_expiry,
$days,
$level
));
$this->logReminder($user, 'document_expiry', $profile, $level, 'all');
}
}
private function checkSponsors()
{
$sponsors = \App\Models\Sponsor::all();
foreach ($sponsors as $sponsor) {
// 1. Check Emirates ID expiry
if ($sponsor->emirates_id_expiry) {
$expiry = Carbon::parse($sponsor->emirates_id_expiry);
$days = (int) now()->startOfDay()->diffInDays($expiry->startOfDay(), false);
$level = $this->getReminderLevel($days, 'reminders.document_expiry_reminder_days', '30,7,3,1');
if ($level && !$this->alreadySent($sponsor, 'document_expiry_emirates', $sponsor, $level)) {
$this->info("Sending {$level} reminder to sponsor {$sponsor->full_name} for Emirates ID expiry.");
$sponsor->notify(new \App\Notifications\DocumentExpiryNotification(
'Emirates ID',
$sponsor->emirates_id_expiry,
$days,
$level
));
$this->logReminder($sponsor, 'document_expiry_emirates', $sponsor, $level, 'all');
}
}
// 2. Check License expiry
if ($sponsor->license_expiry) {
$expiry = Carbon::parse($sponsor->license_expiry);
$days = (int) now()->startOfDay()->diffInDays($expiry->startOfDay(), false);
$level = $this->getReminderLevel($days, 'reminders.document_expiry_reminder_days', '30,7,3,1');
if ($level && !$this->alreadySent($sponsor, 'document_expiry_license', $sponsor, $level)) {
$this->info("Sending {$level} reminder to sponsor {$sponsor->full_name} for License expiry.");
$sponsor->notify(new \App\Notifications\DocumentExpiryNotification(
'Trade License',
$sponsor->license_expiry->toDateString(),
$days,
$level
));
$this->logReminder($sponsor, 'document_expiry_license', $sponsor, $level, 'all');
}
}
}
}
private function checkHireConfirmations()
{
$applications = \App\Models\JobApplication::where('status', 'selected')
->with('jobPost.employer')
->get();
foreach ($applications as $app) {
$job = $app->jobPost;
if (!$job || !$job->start_date) continue;
$employer = $job->employer;
if (!$employer) continue;
$worker = $app->worker;
if (!$worker) continue;
$startDate = Carbon::parse($job->start_date);
$days = (int) now()->startOfDay()->diffInDays($startDate->startOfDay(), false);
$level = $this->getReminderLevel($days, 'reminders.employer_hire_confirm_reminder_days', '3,7');
if (!$level) continue;
if ($this->alreadySent($employer, 'hire_confirmation', $app, $level)) continue;
$this->info("Sending {$level} reminder to employer {$employer->name} to confirm hiring for job {$job->title}.");
$employer->notify(new \App\Notifications\HireConfirmationNotification(
$job->title,
$worker->name,
$days,
$level,
$app->id
));
$this->logReminder($employer, 'hire_confirmation', $app, $level, 'all');
}
}
private function checkJoiningConfirmations()
{
$applications = \App\Models\JobApplication::where('status', 'hired')
->whereNull('joining_confirmed_at')
->with(['jobPost.employer', 'worker'])
->get();
foreach ($applications as $app) {
$job = $app->jobPost;
if (!$job || !$job->start_date) continue;
$worker = $app->worker;
if (!$worker) continue;
$employer = $job->employer;
$employerName = $employer ? $employer->name : 'Employer';
$startDate = Carbon::parse($job->start_date);
$days = (int) now()->startOfDay()->diffInDays($startDate->startOfDay(), false);
$level = $this->getReminderLevel($days, 'reminders.worker_join_confirm_reminder_days', '3,7');
if (!$level) continue;
if ($this->alreadySent($worker, 'joining_confirmation', $app, $level)) continue;
$this->info("Sending {$level} reminder to worker {$worker->name} to confirm joining for job {$job->title}.");
$worker->notify(new \App\Notifications\JoiningConfirmationNotification(
$job->title,
$employerName,
$days,
$level,
$app->id
));
$this->logReminder($worker, 'joining_confirmation', $app, $level, 'all');
}
}
private function checkJobOffers()
{
$offers = \App\Models\JobOffer::where('status', 'pending')
->with(['worker', 'employer'])
->get();
foreach ($offers as $offer) {
$worker = $offer->worker;
if (!$worker) continue;
$employer = $offer->employer;
$employerName = $employer ? $employer->name : 'Employer';
if (!$offer->work_date) continue;
$workDate = Carbon::parse($offer->work_date);
$days = (int) now()->startOfDay()->diffInDays($workDate->startOfDay(), false);
$level = $this->getReminderLevel($days, 'reminders.worker_join_confirm_reminder_days', '3,7');
if (!$level) continue;
if ($this->alreadySent($worker, 'pending_confirmation', $offer, $level)) continue;
$this->info("Sending {$level} reminder to worker {$worker->name} for pending direct offer from {$employerName}.");
$worker->notify(new \App\Notifications\PendingConfirmationNotification(
'accept_offer',
"Pending Job Offer",
"You have a pending job offer from {$employerName} starting on {$offer->work_date->toDateString()}.",
$days,
$level,
get_class($offer),
$offer->id
));
$this->logReminder($worker, 'pending_confirmation', $offer, $level, 'all');
}
}
private function checkWorkerNoResponse()
{
$noResponseDays = (int) config('reminders.worker_no_response_reminder_days', 14);
// 1. Pending direct Job Offers
$offers = \App\Models\JobOffer::where('status', 'pending')
->with(['worker', 'employer'])
->get();
foreach ($offers as $offer) {
$worker = $offer->worker;
if (!$worker) continue;
$employer = $offer->employer;
$employerName = $employer ? $employer->name : 'Employer';
$daysSinceCreation = (int) Carbon::parse($offer->created_at)->startOfDay()->diffInDays(now()->startOfDay(), false);
if ($daysSinceCreation !== $noResponseDays) continue;
$level = $daysSinceCreation === 1 ? '1_day' : "{$daysSinceCreation}_days";
if ($this->alreadySent($worker, 'worker_no_response_offer', $offer, $level)) continue;
$this->info("Sending no-response reminder to worker {$worker->name} for pending direct offer from {$employerName}.");
$worker->notify(new \App\Notifications\WorkerNoResponseNotification(
'worker_no_response_offer',
"Action Required: Pending Job Offer",
"You have a pending job offer from {$employerName} sent on {$offer->created_at->toDateString()} that you have not responded to.",
$daysSinceCreation,
get_class($offer),
$offer->id
));
$this->logReminder($worker, 'worker_no_response_offer', $offer, $level, 'all');
}
// 2. Chat conversations where the last message is from the employer
$conversations = \App\Models\Conversation::with(['employer', 'worker'])->get();
foreach ($conversations as $conv) {
$worker = $conv->worker;
if (!$worker) continue;
$employer = $conv->employer;
$employerName = $employer ? $employer->name : 'Employer';
$lastMessage = $conv->messages()->latest()->first();
if (!$lastMessage) continue;
$isEmployerSender = false;
if ($lastMessage->sender_type === 'App\Models\User' || $lastMessage->sender_id == $conv->employer_id) {
$isEmployerSender = true;
}
if (!$isEmployerSender) continue;
$daysSinceMessage = (int) Carbon::parse($lastMessage->created_at)->startOfDay()->diffInDays(now()->startOfDay(), false);
if ($daysSinceMessage !== $noResponseDays) continue;
$level = $daysSinceMessage === 1 ? '1_day' : "{$daysSinceMessage}_days";
if ($this->alreadySent($worker, 'worker_no_response_chat', $lastMessage, $level)) continue;
$this->info("Sending no-response chat reminder to worker {$worker->name} for conversation with {$employerName}.");
$worker->notify(new \App\Notifications\WorkerNoResponseNotification(
'worker_no_response_chat',
"New Messages: Reply to {$employerName}",
"You have a message from {$employerName} that you have not responded to for {$daysSinceMessage} days.",
$daysSinceMessage,
get_class($lastMessage),
$lastMessage->id
));
$this->logReminder($worker, 'worker_no_response_chat', $lastMessage, $level, 'all');
}
}
private function checkReviewReminders()
{
$eligibilityMonths = (int) config('reminders.review_eligibility_months', 3);
$eligibilityMinutes = config('reminders.review_eligibility_minutes');
$isMinutes = ($eligibilityMinutes !== null && $eligibilityMinutes > 0);
$level = $isMinutes ? "{$eligibilityMinutes}_minutes" : "{$eligibilityMonths}_months";
// 1. Standard Job Applications with status 'hired'
$applications = \App\Models\JobApplication::where('status', 'hired')
->whereNotNull('joining_confirmed_at')
->with(['jobPost.employer', 'worker'])
->get();
foreach ($applications as $app) {
$job = $app->jobPost;
if (!$job) continue;
$employer = $job->employer;
$worker = $app->worker;
if (!$employer || !$worker) continue;
$joiningDate = Carbon::parse($app->joining_confirmed_at);
$eligible = false;
if ($isMinutes) {
$eligible = $joiningDate->diffInMinutes(now(), false) >= $eligibilityMinutes;
} else {
$months = $joiningDate->startOfDay()->diffInMonths(now()->startOfDay(), false);
$eligible = $months >= $eligibilityMonths;
}
if ($eligible) {
// Remind worker to review employer
if (!$this->alreadySent($worker, 'review_employer_reminder', $app, $level)) {
$this->info("Sending review reminder to worker {$worker->name} to review employer {$employer->name}.");
$worker->notify(new \App\Notifications\WorkerReviewReminderNotification(
$employer->name,
$app->id,
$level
));
$this->logReminder($worker, 'review_employer_reminder', $app, $level, 'all');
}
// Remind employer to review worker
if (!$this->alreadySent($employer, 'review_worker_reminder', $app, $level)) {
$this->info("Sending review reminder to employer {$employer->name} to review worker {$worker->name}.");
$employer->notify(new \App\Notifications\EmployerReviewReminderNotification(
$worker->name,
$app->id,
$level
));
$this->logReminder($employer, 'review_worker_reminder', $app, $level, 'all');
}
}
}
// 2. Direct Job Offers with status 'accepted'
$offers = \App\Models\JobOffer::where('status', 'accepted')
->with(['worker', 'employer'])
->get();
foreach ($offers as $offer) {
$worker = $offer->worker;
$employer = $offer->employer;
if (!$worker || !$employer) continue;
$joiningDate = $offer->work_date ? Carbon::parse($offer->work_date) : $offer->updated_at;
$eligible = false;
if ($isMinutes) {
$eligible = $joiningDate->diffInMinutes(now(), false) >= $eligibilityMinutes;
} else {
$months = $joiningDate->startOfDay()->diffInMonths(now()->startOfDay(), false);
$eligible = $months >= $eligibilityMonths;
}
if ($eligible) {
// Remind worker to review employer
if (!$this->alreadySent($worker, 'review_employer_reminder', $offer, $level)) {
$this->info("Sending review reminder to worker {$worker->name} to review employer {$employer->name} (direct offer).");
$worker->notify(new \App\Notifications\WorkerReviewReminderNotification(
$employer->name,
$offer->id,
$level
));
$this->logReminder($worker, 'review_employer_reminder', $offer, $level, 'all');
}
// Remind employer to review worker
if (!$this->alreadySent($employer, 'review_worker_reminder', $offer, $level)) {
$this->info("Sending review reminder to employer {$employer->name} to review worker {$worker->name} (direct offer).");
$employer->notify(new \App\Notifications\EmployerReviewReminderNotification(
$worker->name,
$offer->id,
$level
));
$this->logReminder($employer, 'review_worker_reminder', $offer, $level, 'all');
}
}
}
}
}