windowFor(today()); $force = (bool) $this->option('force'); $dry = (bool) $this->option('dry-run'); if (! $window && ! $force) { $this->info('Outside swap window. Use --force to run anyway. Today: ' . today()->toDateString()); return self::SUCCESS; } $targetSeason = $window['season'] ?? 'winter'; // season of stored sets we want to notify $query = Company::query()->where('status', '!=', 'archived'); if ($slug = $this->option('slug')) $query->where('slug', $slug); $companies = $query->get(); $totalSent = 0; $cooldown = today()->subDays(60); foreach ($companies as $company) { app(TenantManager::class)->setCurrent($company); // Sets currently in storage whose season matches the window target. $sets = TireSet::with(['client', 'vehicle', 'storage']) ->where('season', $targetSeason) ->whereHas('storage', fn ($s) => $s->where('status', 'stored')) ->get() ->filter(fn (TireSet $s) => $s->client && $s->client->status === 'active'); $sentThisTenant = 0; foreach ($sets as $set) { $recent = ServiceReminderSent::where('type', 'tire_swap') ->where('client_id', $set->client_id) ->where('sent_at', '>=', $cooldown) ->exists(); if ($recent) continue; if ($dry) { $this->line(sprintf(' - [%s] set #%d %s · client %s · loc %s', $company->slug, $set->id, $set->sizeLabel(), $set->client?->name ?? '—', $set->currentStorage()?->location ?? '—')); continue; } $ok = $dispatcher->tireSeasonalSwap($set); if ($ok) { ServiceReminderSent::create([ 'company_id' => $company->id, 'vehicle_id' => $set->vehicle_id, 'client_id' => $set->client_id, 'channel' => $set->client?->telegram_chat_id ? 'telegram' : 'email', 'type' => 'tire_swap', 'sent_at' => now(), ]); $sentThisTenant++; } } $this->info(sprintf('[%s] tire-swap reminders sent: %d', $company->slug, $sentThisTenant)); $totalSent += $sentThisTenant; } $this->info("Total tire-swap reminders sent: {$totalSent}" . ($dry ? ' (dry run)' : '')); return self::SUCCESS; } /** Returns ['season' => 'winter'|'summer'] if today is in a swap window, else null. */ private function windowFor(Carbon $today): ?array { // Feb 15 – Mar 15 → notify WINTER sets (swap to summer). $springStart = Carbon::create($today->year, 2, 15); $springEnd = Carbon::create($today->year, 3, 15); if ($today->between($springStart, $springEnd)) return ['season' => 'winter']; // Sep 15 – Oct 15 → notify SUMMER sets (swap to winter). $autumnStart = Carbon::create($today->year, 9, 15); $autumnEnd = Carbon::create($today->year, 10, 15); if ($today->between($autumnStart, $autumnEnd)) return ['season' => 'summer']; return null; } }