middleware('auth'); $this->middleware('admin')->except(['show', 'track']); } /** * Test the DHL API login credentials and return a JSON response. * * @return \Illuminate\Http\JsonResponse */ public function testLogin() { try { // Get DHL configuration with admin settings $settingController = new \App\Http\Controllers\SettingController(); $dhlConfig = $settingController->getDhlConfig(); // Create DhlClient with merged configuration $dhlClient = new \Acme\Dhl\Support\DhlClient( $dhlConfig['base_url'], $dhlConfig['api_key'], $dhlConfig['username'], $dhlConfig['password'] ); // Test the connection $connectionTest = $dhlClient->testConnection(); if ($connectionTest) { $result = [ 'success' => true, 'message' => 'DHL API Verbindung erfolgreich getestet!', 'details' => [ 'base_url' => $dhlConfig['base_url'], 'using_admin_config' => !empty($dhlConfig['api_key']) ] ]; } else { $result = [ 'success' => false, 'message' => 'DHL API Verbindung fehlgeschlagen. Prüfen Sie Ihre Zugangsdaten.' ]; } return response()->json($result); } catch (Exception $e) { Log::error('[DHL Controller] Test login failed', [ 'error' => $e->getMessage() ]); return response()->json([ 'success' => false, 'message' => 'DHL API Test fehlgeschlagen: ' . $e->getMessage() ], 500); } } /** * Display the DHL Cockpit (main overview) * * @param Request $request * @return View */ public function index(Request $request): View { // Statistics for dashboard widgets $stats = [ 'total_shipments' => DhlShipment::count(), 'pending_shipments' => DhlShipment::where('status', 'pending')->count(), 'shipped_today' => DhlShipment::whereDate('created_at', today())->count(), 'returns_count' => DhlShipment::where('type', 'return')->count(), ]; return view('admin.dhl.cockpit', compact('stats')); } /** * Provides data for the DHL Cockpit DataTable. * * @param Request $request * @return \Illuminate\Http\JsonResponse */ public function datatable(Request $request): JsonResponse { $query = DhlShipment::with(['shoppingOrder.shopping_user']) ->select('dhl_package_shipments.*') // Explicitly select to avoid conflicts ->orderBy('created_at', 'desc'); // Apply filters from the request if ($request->filled('type')) { $query->where('type', $request->get('type')); } if ($request->filled('status')) { $query->where('status', $request->get('status')); } if ($request->filled('date_from')) { $query->whereDate('created_at', '>=', $request->get('date_from')); } if ($request->filled('date_to')) { $query->whereDate('created_at', '<=', $request->get('date_to')); } if ($request->filled('search')) { $search = $request->get('search'); $query->where(function ($q) use ($search) { $q->where('dhl_shipment_no', 'LIKE', "%{$search}%") ->orWhere('id', 'LIKE', "%{$search}%") ->orWhereHas('shoppingOrder', function ($orderQuery) use ($search) { $orderQuery->where('id', $search); }); }); } return DataTables::eloquent($query) ->addColumn('checkbox', function ($shipment) { return ''; }) ->editColumn('id', function ($shipment) { return '#' . $shipment->id . ''; }) ->addColumn('type', function ($shipment) { if ($shipment->type == 'outbound') { return ' Ausgehend'; } else { return ' Retoure'; } }) ->addColumn('order', function ($shipment) { if ($shipment->order_id) { return '#' . $shipment->order_id . ''; } return 'N/A'; }) ->addColumn('customer', function ($shipment) { if ($shipment->shoppingOrder && $shipment->shoppingOrder->shopping_user) { return e($shipment->shoppingOrder->shopping_user->billing_firstname) . ' ' . e($shipment->shoppingOrder->shopping_user->billing_lastname) . '
' . e($shipment->shoppingOrder->shopping_user->billing_email) . ''; } return 'Unbekannt'; }) ->editColumn('dhl_shipment_no', function ($shipment) { return $shipment->dhl_shipment_no ? '' . e($shipment->dhl_shipment_no) . '' : '-'; }) ->addColumn('status', function ($shipment) { $statusMap = [ 'pending' => ['class' => 'warning', 'text' => 'Wartend'], 'created' => ['class' => 'success', 'text' => 'Erstellt'], 'shipped' => ['class' => 'primary', 'text' => 'Versendet'], 'delivered' => ['class' => 'info', 'text' => 'Zugestellt'], 'cancelled' => ['class' => 'secondary', 'text' => 'Storniert'], 'failed' => ['class' => 'danger', 'text' => 'Fehler'], ]; $statusInfo = $statusMap[$shipment->status] ?? ['class' => 'light', 'text' => e($shipment->status)]; return '' . $statusInfo['text'] . ''; }) ->addColumn('tracking_status', function ($shipment) { if ($shipment->tracking_status) { return '' . e($shipment->tracking_status) . '' . ($shipment->last_tracked_at ? '
' . $shipment->last_tracked_at->format('d.m.Y H:i') . '' : ''); } return '-'; }) ->editColumn('weight_kg', function ($shipment) { return number_format($shipment->weight_kg, 2) . ' kg'; }) ->editColumn('created_at', function ($shipment) { return $shipment->created_at->format('d.m.Y H:i'); }) ->addColumn('actions', function ($shipment) { $buttons = '
'; $buttons .= ''; if ($shipment->label_path) { $buttons .= ''; } if ($shipment->canCancel()) { $buttons .= ''; } if ($shipment->type == 'outbound' && !$shipment->returns()->count()) { $buttons .= ''; } $buttons .= '
'; return $buttons; }) ->rawColumns(['checkbox', 'id', 'type', 'order', 'customer', 'dhl_shipment_no', 'status', 'tracking_status', 'actions']) ->make(true); } /** * Show the form for creating a new shipment * * @param ShoppingOrder $order * @return View */ public function create(ShoppingOrder $order): View|\Illuminate\Http\RedirectResponse { // Check if order already has a shipment $existingShipment = DhlShipment::where('shopping_order_id', $order->id) ->where('type', 'outbound') ->first(); if ($existingShipment) { return redirect()->route('admin.dhl.show', $existingShipment) ->with('warning', 'Für diese Bestellung existiert bereits eine Sendung.'); } return view('admin.dhl.create', compact('order')); } /** * Store a new shipment (async via queue) * * @param Request $request * @return JsonResponse */ public function store(Request $request): JsonResponse { try { // Use DhlModalService for validation $dhlModalService = new DhlModalService(); $validationResult = $dhlModalService->validateShipmentData($request->all()); if (!$validationResult['valid']) { return response()->json([ 'success' => false, 'message' => 'Validierungsfehler: ' . implode(', ', $validationResult['errors']) ], 422); } // Basic Laravel validation as fallback $request->validate([ 'order_id' => 'required|exists:shopping_orders,id', 'weight' => 'required|numeric|min:0.1|max:31.5', 'product_code' => 'sometimes|string', 'priority' => 'sometimes|string|in:normal,high', 'auto_track' => 'sometimes|boolean', // Shipping address validation 'shipping_firstname' => 'required|string|max:50', 'shipping_lastname' => 'required|string|max:50', 'shipping_company' => 'nullable|string|max:100', 'shipping_address' => 'required|string|max:100', 'shipping_houseNumber' => 'required|string|max:50', 'shipping_zipcode' => 'required|string|max:10', 'shipping_city' => 'required|string|max:50', 'shipping_country_id' => 'required|exists:countries,id', 'shipping_phone' => 'nullable|string|max:20', ]); $order = ShoppingOrder::findOrFail($request->order_id); // Check if shipment already exists /* $existingShipment = DhlShipment::where('shopping_order_id', $order->id) ->where('type', 'outbound') ->first(); if ($existingShipment) { return response()->json([ 'success' => false, 'message' => 'Für diese Bestellung existiert bereits eine Sendung.' ], 422); } */ // Use service to prepare address data $shippingAddress = $dhlModalService->prepareAddressForApi($request->all()); // Prepare options for shipment creation $options = [ 'product_code' => $request->get('product_code', 'V01PAK'), 'priority' => $request->get('priority', 'normal'), 'auto_track' => $request->get('auto_track', true), 'shipping_address' => $shippingAddress, 'services' => $request->get('services', []), 'dimensions' => $request->only(['length', 'width', 'height']) ]; // Use DhlShipmentService (handles queue/sync automatically based on config) $dhlShipmentService = new DhlShipmentService(); $result = $dhlShipmentService->createShipment($order, (float) $request->weight, $options); Log::info('[DHL Controller] Shipment creation processed', [ 'order_id' => $order->id, 'weight' => $request->weight, 'queued' => $result['queued'] ?? false, 'success' => $result['success'] ?? false, ]); return response()->json($result); } catch (Exception $e) { Log::error('[DHL Controller] Failed to dispatch shipment creation', [ 'error' => $e->getMessage(), 'order_id' => $request->order_id ?? 'unknown', ]); return response()->json([ 'success' => false, 'message' => 'Fehler beim Erstellen der Sendung: ' . $e->getMessage() ], 500); } } /** * Display the specified shipment * * @param DhlShipment $shipment * @return View */ public function show(DhlShipment $shipment): View { $shipment->load(['shoppingOrder.shopping_user', 'relatedShipment']); return view('admin.dhl.show', compact('shipment')); } /** * Cancel the specified shipment * * @param Request $request * @param DhlShipment $shipment * @return JsonResponse */ public function cancel(Request $request, DhlShipment $shipment): JsonResponse { try { // Validate cancellation is possible if (!$shipment->canCancel()) { return response()->json([ 'success' => false, 'message' => 'Diese Sendung kann nicht mehr storniert werden.' ], 422); } // Dispatch cancellation job $options = [ 'priority' => $request->get('priority', 'normal') ]; CancelShipmentJob::dispatch($shipment, $options); Log::info('[DHL Controller] Shipment cancellation job dispatched', [ 'shipment_id' => $shipment->id, 'shipment_number' => $shipment->shipment_number, ]); return response()->json([ 'success' => true, 'message' => 'Sendung wird storniert...' ]); } catch (Exception $e) { Log::error('[DHL Controller] Failed to dispatch shipment cancellation', [ 'error' => $e->getMessage(), 'shipment_id' => $shipment->id, ]); return response()->json([ 'success' => false, 'message' => 'Fehler beim Stornieren der Sendung: ' . $e->getMessage() ], 500); } } /** * Create return label for the specified shipment * * @param Request $request * @param DhlShipment $shipment * @return JsonResponse */ public function createReturnLabel(Request $request, DhlShipment $shipment): JsonResponse { try { // Validate return label creation is possible if ($shipment->type !== 'outbound') { return response()->json([ 'success' => false, 'message' => 'Retourenlabels können nur für ausgehende Sendungen erstellt werden.' ], 422); } // Check if return label already exists $existingReturn = DhlShipment::where('related_shipment_id', $shipment->id) ->where('type', 'return') ->first(); if ($existingReturn) { return response()->json([ 'success' => false, 'message' => 'Für diese Sendung existiert bereits ein Retourenlabel.' ], 422); } // Dispatch return label creation job $options = [ 'auto_track' => $request->get('auto_track', false), 'priority' => $request->get('priority', 'normal') ]; CreateReturnLabelJob::dispatch($shipment, $options); Log::info('[DHL Controller] Return label creation job dispatched', [ 'original_shipment_id' => $shipment->id, 'shipment_number' => $shipment->shipment_number, ]); return response()->json([ 'success' => true, 'message' => 'Retourenlabel wird erstellt...' ]); } catch (Exception $e) { Log::error('[DHL Controller] Failed to dispatch return label creation', [ 'error' => $e->getMessage(), 'shipment_id' => $shipment->id, ]); return response()->json([ 'success' => false, 'message' => 'Fehler beim Erstellen des Retourenlabels: ' . $e->getMessage() ], 500); } } /** * Update tracking status for the specified shipment * * @param DhlShipment $shipment * @return JsonResponse */ public function updateTracking(DhlShipment $shipment): JsonResponse { try { if (!$shipment->tracking_number) { return response()->json([ 'success' => false, 'message' => 'Keine Tracking-Nummer verfügbar.' ], 422); } // Dispatch tracking update job TrackShipmentJob::dispatch($shipment, ['auto_retrack' => false]); Log::info('[DHL Controller] Tracking update job dispatched', [ 'shipment_id' => $shipment->id, 'tracking_number' => $shipment->tracking_number, ]); return response()->json([ 'success' => true, 'message' => 'Tracking-Informationen werden aktualisiert...' ]); } catch (Exception $e) { Log::error('[DHL Controller] Failed to dispatch tracking update', [ 'error' => $e->getMessage(), 'shipment_id' => $shipment->id, ]); return response()->json([ 'success' => false, 'message' => 'Fehler beim Aktualisieren der Tracking-Informationen: ' . $e->getMessage() ], 500); } } /** * Download shipping label * * @param DhlShipment $shipment * @return Response */ public function downloadLabel(DhlShipment $shipment): Response { try { if (!$shipment->label_path || !Storage::exists($shipment->label_path)) { abort(404, 'Versandlabel nicht gefunden.'); } $labelContent = Storage::get($shipment->label_path); $filename = sprintf( 'dhl-label-%s-%s.pdf', $shipment->type, $shipment->shipment_number ?: $shipment->id ); return response($labelContent, 200) ->header('Content-Type', 'application/pdf') ->header('Content-Disposition', "attachment; filename=\"{$filename}\""); } catch (Exception $e) { Log::error('[DHL Controller] Failed to download label', [ 'error' => $e->getMessage(), 'shipment_id' => $shipment->id, 'label_path' => $shipment->label_path, ]); abort(500, 'Fehler beim Download des Versandlabels.'); } } /** * Batch operations (multiple shipments) * * @param Request $request * @return JsonResponse */ public function batchAction(Request $request): JsonResponse { try { $request->validate([ 'action' => 'required|in:cancel,download_labels,update_tracking', 'shipment_ids' => 'required|array|min:1', 'shipment_ids.*' => 'exists:dhl_package_shipments,id', ]); $shipmentIds = $request->shipment_ids; $action = $request->action; $processed = 0; $errors = []; foreach ($shipmentIds as $shipmentId) { try { $shipment = DhlShipment::findOrFail($shipmentId); switch ($action) { case 'cancel': if ($shipment->canCancel()) { CancelShipmentJob::dispatch($shipment); $processed++; } else { $errors[] = "Sendung {$shipment->shipment_number} kann nicht storniert werden."; } break; case 'update_tracking': if ($shipment->tracking_number) { TrackShipmentJob::dispatch($shipment, ['auto_retrack' => false]); $processed++; } else { $errors[] = "Sendung {$shipment->shipment_number} hat keine Tracking-Nummer."; } break; case 'download_labels': // This would require ZIP creation - implement if needed $errors[] = "Stapel-Download noch nicht implementiert."; break; } } catch (Exception $e) { $errors[] = "Fehler bei Sendung {$shipmentId}: " . $e->getMessage(); } } Log::info('[DHL Controller] Batch action executed', [ 'action' => $action, 'processed' => $processed, 'errors_count' => count($errors), ]); return response()->json([ 'success' => $processed > 0, 'message' => sprintf('%d Sendungen verarbeitet.', $processed), 'processed' => $processed, 'errors' => $errors, ]); } catch (Exception $e) { Log::error('[DHL Controller] Batch action failed', [ 'error' => $e->getMessage(), 'action' => $request->action ?? 'unknown', ]); return response()->json([ 'success' => false, 'message' => 'Fehler bei der Stapelverarbeitung: ' . $e->getMessage() ], 500); } } /** * Public tracking page (for customers) * * @param Request $request * @return View|JsonResponse */ public function track(Request $request): View|JsonResponse { if ($request->expectsJson()) { $request->validate([ 'tracking_number' => 'required|string|min:10', ]); try { $shipment = DhlShipment::where('tracking_number', $request->tracking_number)->first(); if (!$shipment) { return response()->json([ 'success' => false, 'message' => 'Sendung nicht gefunden.' ], 404); } // Dispatch tracking update TrackShipmentJob::dispatch($shipment, ['auto_retrack' => false]); return response()->json([ 'success' => true, 'data' => [ 'tracking_number' => $shipment->tracking_number, 'status' => $shipment->status, 'tracking_status' => $shipment->tracking_status, 'last_tracked_at' => $shipment->last_tracked_at?->format('d.m.Y H:i'), ] ]); } catch (Exception $e) { Log::error('[DHL Controller] Public tracking failed', [ 'error' => $e->getMessage(), 'tracking_number' => $request->tracking_number ?? 'unknown', ]); return response()->json([ 'success' => false, 'message' => 'Fehler beim Abrufen der Tracking-Informationen.' ], 500); } } return view('public.tracking'); } }