diff --git a/app/Jobs/SendScheduledWhatsAppJob.php b/app/Jobs/SendScheduledWhatsAppJob.php index 294dd5a..61e1580 100644 --- a/app/Jobs/SendScheduledWhatsAppJob.php +++ b/app/Jobs/SendScheduledWhatsAppJob.php @@ -2,16 +2,18 @@ namespace App\Jobs; -use App\Models\Plan; use App\Models\User; use App\Models\UserNotificationPreference; use App\Services\PlanFeatures; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Queue\Queueable; /** * Fan-out job for scheduled WhatsApp updates (morning / evening). - * Finds all eligible users and dispatches DispatchUserNotificationJob per user. + * Dispatches one DispatchUserNotificationJob per eligible user so each + * user's send is its own queueable unit (independent retry, no shared + * failure mode across the cohort). * * Scheduled at 07:30 (morning) and 18:00 (evening) via routes/console.php. */ @@ -28,36 +30,24 @@ final class SendScheduledWhatsAppJob implements ShouldQueue { $triggerType = $this->period === 'morning' ? 'scheduled_morning' : 'scheduled_evening'; - // Plans that allow scheduled WhatsApp updates - $eligiblePlanNames = Plan::where('active', true) - ->where('whatsapp_scheduled_updates', '>', 0) - ->pluck('name') - ->all(); - - if (empty($eligiblePlanNames)) { - return; - } - - // Users who have whatsapp preference enabled + // Candidates: users who have explicitly opted in to WhatsApp. + // Per-user tier + daily-limit + scheduled-updates checks happen via + // canSendNow('whatsapp'); that single call covers tier eligibility + // (canUseChannel) AND today's notification_log count. $userIds = UserNotificationPreference::where('channel', 'whatsapp') ->where('enabled', true) ->distinct() ->pluck('user_id'); User::whereIn('id', $userIds) - ->each(function (User $user) use ($triggerType, $eligiblePlanNames): void { - $features = PlanFeatures::for($user); + ->chunkById(500, function (Collection $users) use ($triggerType): void { + foreach ($users as $user) { + if (! PlanFeatures::for($user)->canSendNow('whatsapp')) { + continue; + } - // Skip if their tier isn't eligible or daily limit is hit - if (! in_array($features->tier(), $eligiblePlanNames, strict: true)) { - return; + DispatchUserNotificationJob::dispatch($user, $triggerType, fuelType: 'all'); } - - if (! $features->canSendNow('whatsapp')) { - return; - } - - DispatchUserNotificationJob::dispatch($user, $triggerType, fuelType: 'all'); }); } }