475 lines
19 KiB
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');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|