option('days'); $sendEmails = $this->option('send-emails'); $dryRun = $this->option('dry-run'); $testEmail = $this->option('test-email'); $orderId = $this->option('order'); $force = $this->option('force'); $staleDays = (int) $this->option('stale-days'); $this->info('DHL Tracking Update gestartet'); $this->info("Optionen: --days={$days}, --send-emails=".($sendEmails ? 'ja' : 'nein') .', --dry-run='.($dryRun ? 'ja' : 'nein') .', --force='.($force ? 'ja' : 'nein') .", --stale-days={$staleDays}"); if ($testEmail) { $this->info("Test-Modus: E-Mails werden an {$testEmail} gesendet"); } if ($orderId) { $this->info("Filter: Nur Order-ID {$orderId}"); } $this->newLine(); // Step 1: Mark stale shipments as completed (before main query) $staleCompleted = $this->markStaleShipmentsCompleted($staleDays, $dryRun); // Step 2: Build query for shipments that need tracking update $query = $this->buildShipmentQuery($days, $orderId, $force); $shipments = $query->orderBy('created_at', 'desc')->get(); // Count total active shipments for statistics (before interval filter) $totalActive = DhlShipment::active() ->whereNull('tracking_completed_at') ->where('created_at', '>=', now()->subDays($days)) ->whereNotNull('dhl_shipment_no') ->count(); $total = $shipments->count(); $skippedByInterval = $totalActive - $total; $this->info("Aktive Sendungen gesamt: {$totalActive}"); $this->info('Übersprungen (Intervall): '.max(0, $skippedByInterval)); $this->info("Zu aktualisieren: {$total}"); if ($total === 0) { $this->info('Keine Sendungen zum Aktualisieren gefunden.'); $this->printSummary(0, ['updated' => 0, 'failed' => 0, 'completed' => 0, 'emails_sent' => 0, 'skipped' => 0], $staleCompleted, max(0, $skippedByInterval)); return self::SUCCESS; } $trackingService = new DhlTrackingService; $stats = [ 'updated' => 0, 'failed' => 0, 'completed' => 0, 'emails_sent' => 0, 'skipped' => 0, ]; if ($dryRun) { $stats['skipped'] = $total; $this->info("Dry-Run: {$total} Sendungen würden aktualisiert."); } else { // Collect old statuses for email decision $oldStatuses = $shipments->pluck('status', 'id')->toArray(); // Use batch API for efficient processing $this->info('Starte Batch-Tracking-Update...'); $bar = $this->output->createProgressBar($total); $bar->start(); $batchResult = $trackingService->updateTrackingBatch($shipments); $stats['updated'] = $batchResult['updated']; $stats['failed'] = $batchResult['failed']; $stats['completed'] = $batchResult['completed']; $bar->advance($total); $bar->finish(); $this->newLine(2); // Send tracking emails if enabled if ($sendEmails) { $this->info('Prüfe E-Mail-Versand...'); foreach ($shipments as $shipment) { $shipment->refresh(); $oldStatus = $oldStatuses[$shipment->id] ?? ''; if ($this->shouldSendEmail($shipment, $oldStatus)) { try { $this->sendTrackingEmail($shipment, $testEmail); $stats['emails_sent']++; } catch (\Exception $e) { Log::error('[DHL Cron] Failed to send tracking email', [ 'shipment_id' => $shipment->id, 'error' => $e->getMessage(), ]); } } } } } $this->printSummary($total, $stats, $staleCompleted, max(0, $skippedByInterval)); Log::info('[DHL Cron] Tracking update completed', array_merge($stats, [ 'total' => $total, 'stale_completed' => $staleCompleted, 'skipped_interval' => max(0, $skippedByInterval), ])); return self::SUCCESS; } /** * Build the shipment query with or without interval filtering. */ private function buildShipmentQuery(int $days, ?string $orderId, bool $force) { if ($force) { // --force: Alle aktiven Sendungen ohne Intervall-Filter $query = DhlShipment::active() ->whereNull('tracking_completed_at') ->where('created_at', '>=', now()->subDays($days)) ->whereNotNull('dhl_shipment_no'); } else { // Normal: Status-basierte Intervalle beachten $query = DhlShipment::needsTrackingUpdate() ->where('created_at', '>=', now()->subDays($days)); } // Filter nach Order-ID wenn angegeben if ($orderId) { $query->where('order_id', $orderId); } return $query; } /** * Mark shipments as tracking-completed if they haven't changed status * for a given number of days (stale shipments). */ private function markStaleShipmentsCompleted(int $staleDays, bool $dryRun): int { $staleShipments = DhlShipment::active() ->whereNull('tracking_completed_at') ->whereNotNull('last_tracked_at') ->where('last_tracked_at', '<', now()->subDays($staleDays)) ->where('created_at', '<', now()->subDays($staleDays)) ->get(); $count = $staleShipments->count(); if ($count > 0) { $this->warn("Veraltete Sendungen gefunden: {$count} (>{$staleDays} Tage ohne Änderung)"); if (! $dryRun) { foreach ($staleShipments as $shipment) { $shipment->markTrackingCompleted(); Log::info('[DHL Cron] Stale shipment tracking completed', [ 'shipment_id' => $shipment->id, 'dhl_shipment_no' => $shipment->dhl_shipment_no, 'status' => $shipment->status, 'last_tracked_at' => $shipment->last_tracked_at?->toDateTimeString(), ]); } $this->info(" → {$count} Sendungen als Tracking-abgeschlossen markiert."); } else { $this->info(" → Dry-Run: {$count} Sendungen würden als abgeschlossen markiert."); } } $this->newLine(); return $count; } /** * Print the final summary table. */ private function printSummary(int $total, array $stats, int $staleCompleted, int $skippedByInterval): void { $this->info('Zusammenfassung:'); $this->table( ['Metrik', 'Anzahl'], [ ['Zu aktualisieren', $total], ['Aktualisiert', $stats['updated']], ['Fehlgeschlagen', $stats['failed']], ['Tracking abgeschlossen', $stats['completed']], ['E-Mails gesendet', $stats['emails_sent']], ['Übersprungen (Dry-Run)', $stats['skipped']], ['Übersprungen (Intervall)', $skippedByInterval], ['Veraltet → abgeschlossen', $staleCompleted], ] ); } /** * Prüft ob eine E-Mail gesendet werden soll */ private function shouldSendEmail(DhlShipment $shipment, string $oldStatus): bool { return $shipment->shouldTriggerTrackingEmail($oldStatus); } /** * Sendet die Tracking-E-Mail (mit Unterstützung für mehrere Sendungen pro Bestellung) */ private function sendTrackingEmail(DhlShipment $shipment, ?string $testEmail = null): void { try { $order = $shipment->shoppingOrder; // Determine recipient email: test email > shipment email > shopping user email $recipientEmail = null; if ($testEmail) { $recipientEmail = $testEmail; } elseif (! empty($shipment->email)) { $recipientEmail = $shipment->email; } elseif ($order->shopping_user && ! empty($order->shopping_user->email)) { $recipientEmail = $order->shopping_user->email; } if (! $recipientEmail) { Log::warning('[DHL Cron] Cannot send email - no recipient', [ 'shipment_id' => $shipment->id, ]); return; } // Sammle alle Sendungen für diese Bestellung, die noch keine E-Mail erhalten haben $allShipments = DhlShipment::where('order_id', $order->id) ->whereIn('status', DhlShipment::TRACKING_EMAIL_TRIGGER_STATUSES) ->whereNotNull('dhl_shipment_no') ->whereNull('tracking_email_sent_at') ->get(); // Wenn keine Sendungen gefunden, nutze nur die aktuelle if ($allShipments->isEmpty()) { $allShipments = collect([$shipment]); } // Sende E-Mail mit allen Sendungen Mail::to($recipientEmail)->send(new MailDhlTracking($allShipments, $order)); // Markiere alle Sendungen als versendet foreach ($allShipments as $s) { $s->markTrackingEmailSent('auto', $recipientEmail, $allShipments); } Log::info('[DHL Cron] Tracking email sent automatically', [ 'shipment_ids' => $allShipments->pluck('id')->toArray(), 'shipments_count' => $allShipments->count(), 'dhl_shipment_nos' => $allShipments->pluck('dhl_shipment_no')->toArray(), 'email' => $recipientEmail, 'is_test' => ! is_null($testEmail), ]); if ($allShipments->count() > 1) { $this->line(" → E-Mail mit {$allShipments->count()} Sendungen gesendet an: {$recipientEmail}"); } else { $this->line(" → E-Mail gesendet an: {$recipientEmail}"); } } catch (\Exception $e) { Log::error('[DHL Cron] Failed to send tracking email', [ 'shipment_id' => $shipment->id, 'error' => $e->getMessage(), ]); } } }