<?php
 namespace App\Console\Commands; use App\Jobs\SendEmailJob; use App\Models\ClientProduct; use App\Models\ClientProductTrial; use Illuminate\Console\Command; class SendTrialEndingSoonRemindersCommand extends Command { protected $signature = 'trial:send-ending-soon-reminders
                            {--dry-run : Show what would be sent without actually sending}'; protected $description = 'Send "trial ending soon" reminder emails to clients whose free trial ends in 3 days (PRC-539)'; private const DAYS_BEFORE_END = 3; public function handle(): int { $dryRun = $this->option('dry-run'); $targetDate = now()->addDays(self::DAYS_BEFORE_END)->toDateString(); $this->info(sprintf( 'Checking for trials ending on %s (in %d days)…%s', $targetDate, self::DAYS_BEFORE_END, $dryRun ? ' [DRY RUN]' : '' )); $trials = ClientProductTrial::where('trial_mode', 'free') ->where('status', 'active') ->whereDate('ends_at', $targetDate) ->whereNull('ending_soon_sent_at') ->with([ 'clientProduct.client.currency', 'clientProduct.product', ]) ->get(); if ($trials->isEmpty()) { $this->info('No trials ending in ' . self::DAYS_BEFORE_END . ' days — nothing to send.'); return Command::SUCCESS; } $this->info("Found {$trials->count()} trial(s) to notify."); $sentCount = 0; $skippedCount = 0; foreach ($trials as $trial) { $cp = $trial->clientProduct; if (! $cp) { $this->warn("Trial #{$trial->id}: no ClientProduct — skipped."); $skippedCount++; continue; } $client = $cp->client; $product = $cp->product; if (! $client || ! $product) { $this->warn("Trial #{$trial->id} (cp #{$cp->id}): missing client or product — skipped."); $skippedCount++; continue; } if ($dryRun) { $this->line("Would send trial_ending_soon to {$client->email} (cp #{$cp->id}, trial #{$trial->id})"); $sentCount++; continue; } try { $currencyCode = $client->currency?->code ?? 'USD'; SendEmailJob::dispatch( 'Trial Ending Soon', 'client_product', $cp->id, [ 'CLIENT_NAME' => trim($client->first_name . ' ' . $client->last_name), 'PRODUCT_NAME' => $product->name, 'TRIAL_ENDS_AT' => $trial->ends_at ? $trial->ends_at->format('Y-m-d') : '—', 'TRIAL_DAYS' => $product->trial_period_days ?? '—', 'RECURRING_AMOUNT' => number_format((float) $cp->recurring_amount, 2) . ' ' . $currencyCode, 'RECURRING_CYCLE' => $cp->billingcycle ?? 'monthly', ] ); $trial->update(['ending_soon_sent_at' => now()]); $this->info("Dispatched trial_ending_soon for cp #{$cp->id} ({$client->email})"); $sentCount++; } catch (\Throwable $e) { $this->error("Failed for trial #{$trial->id} (cp #{$cp->id}): {$e->getMessage()}"); $skippedCount++; } } $this->newLine(); $this->info("Trial ending-soon reminders complete: sent={$sentCount}, skipped/failed={$skippedCount}"); return Command::SUCCESS; } } 