where('status', '!=', 'archived'); if ($slug = $this->option('slug')) { $query->where('slug', $slug); } $companies = $query->get(); $dry = (bool) $this->option('dry-run'); $totalSent = 0; foreach ($companies as $company) { app(TenantManager::class)->setCurrent($company); $settings = (array) ($company->settings ?? []); $reminderDays = (int) data_get($settings, 'reminder.after_days', 365); $cooldownDays = (int) data_get($settings, 'reminder.cooldown_days', 30); $cutoff = Carbon::now()->subDays($reminderDays); $cooldown = Carbon::now()->subDays($cooldownDays); // Pick vehicles whose last *closed* WO was before $cutoff (or never). $vehicles = Vehicle::with('client') ->whereHas('client', fn ($q) => $q->where('status', 'active')) ->get(); $sentThisTenant = 0; foreach ($vehicles as $v) { $lastClosedAt = WorkOrder::where('vehicle_id', $v->id) ->whereNotNull('closed_at') ->max('closed_at'); if (! $lastClosedAt) continue; // never serviced — skip (handled by other logic) if (Carbon::parse($lastClosedAt)->gt($cutoff)) continue; $recent = ServiceReminderSent::where('vehicle_id', $v->id) ->where('sent_at', '>=', $cooldown) ->exists(); if ($recent) continue; if ($dry) { $this->line(" - [{$company->slug}] Vehicle #{$v->id} {$v->plate} last serviced {$lastClosedAt}"); continue; } $ok = $dispatcher->serviceReminder($v, 'general'); if ($ok) { ServiceReminderSent::create([ 'company_id' => $company->id, 'vehicle_id' => $v->id, 'client_id' => $v->client_id, 'channel' => $v->client?->telegram_chat_id ? 'telegram' : 'email', 'type' => 'general', 'sent_at' => now(), ]); $sentThisTenant++; } } $this->info(sprintf('[%s] reminders sent: %d', $company->slug, $sentThisTenant)); $totalSent += $sentThisTenant; } $this->info("Total reminders sent: {$totalSent}" . ($dry ? ' (dry run)' : '')); return self::SUCCESS; } }