config = config('dhl'); } /** * Prepare modal data for DHL shipment creation * * @param mixed $id Order ID or 'new' * @param array $data Additional data from the request * @return array Prepared data for the view * * @throws Exception */ public function prepareModalData($id, array $data): array { $result = [ 'order' => null, 'orderWeight' => 1.0, 'shippingAddress' => null, 'availableCountries' => $this->getAvailableCountries(), 'productCodes' => $this->getAvailableProductCodes(), 'productSuggestions' => (new DhlProductResolver)->getProductSuggestionsByCountry(), 'selectedProductCode' => null, 'errors' => [], 'warnings' => [], 'existingShipments' => [], 'modalMode' => 'search', // 'search', 'create', 'info' ]; // If no order ID or 'new', return empty data for order selection if (! $id || $id === 'new') { return $result; } try { // Load and validate order $order = $this->loadOrder($id); if (! $order) { $result['errors'][] = "Bestellung #{$id} wurde nicht gefunden."; return $result; } $result['order'] = $order; // Check for existing DHL shipments $existingShipments = $this->getExistingShipments($order); $result['existingShipments'] = $existingShipments; // Check if force_create is requested $forceCreate = isset($data['force_create']) && $data['force_create']; // Determine modal mode based on existing shipments and force_create if (! empty($existingShipments) && ! $forceCreate) { $result['modalMode'] = 'info'; Log::info('[DHL Modal] Order has existing shipments, showing info mode', [ 'order_id' => $order->id, 'shipment_count' => count($existingShipments), ]); } else { $result['modalMode'] = 'create'; // Calculate order weight $result['orderWeight'] = $this->calculateOrderWeight($order); // Process and validate shipping address $result['shippingAddress'] = $this->processShippingAddress($order); $result['selectedProductCode'] = $this->getSuggestedProductCode($result['shippingAddress']); // Validate address completeness $addressValidation = $this->validateAddress($result['shippingAddress']); if (! $addressValidation['valid']) { $result['errors'] = array_merge($result['errors'], $addressValidation['errors']); } if (! empty($addressValidation['warnings'])) { $result['warnings'] = array_merge($result['warnings'], $addressValidation['warnings']); } Log::info('[DHL Modal] Prepared modal data for creation', [ 'order_id' => $order->id, 'weight' => $result['orderWeight'], 'address_valid' => empty($result['errors']), ]); } } catch (Exception $e) { Log::error('[DHL Modal] Error preparing modal data', [ 'order_id' => $id, 'error' => $e->getMessage(), ]); $result['errors'][] = 'Fehler beim Laden der Bestelldaten: '.$e->getMessage(); } return $result; } /** * Load order with required relationships * * @param mixed $id */ private function loadOrder($id): ?ShoppingOrder { return ShoppingOrder::with([ 'shopping_order_items.product', 'shopping_user', 'dhlShipments', // Include DHL shipments ])->find($id); } /** * Get existing DHL shipments for the order */ private function getExistingShipments(ShoppingOrder $order): array { $shipments = $order->dhlShipments() ->orderBy('created_at', 'desc') ->get(); return $shipments->map(function ($shipment) { return [ 'id' => $shipment->id, 'shipment_number' => $shipment->dhl_shipment_no, 'tracking_number' => $shipment->routing_code, 'type' => $shipment->type, 'status' => $shipment->status, 'status_translated' => $shipment->getStatusTranslation(), 'type_translated' => $shipment->getTypeTranslation(), 'product_code_translated' => $shipment->getProductCodeTranslation(), 'weight' => $shipment->weight_kg, 'product_code' => $shipment->product_code, 'label_path' => $shipment->label_path, 'created_at' => $shipment->created_at->toDateTimeString(), 'tracking_status' => $shipment->tracking_status, 'tracking_status_translated' => $shipment->tracking_status ? \Acme\Dhl\Models\DhlShipment::getStatusTranslationFor($shipment->tracking_status) : null, 'last_tracked_at' => $shipment->last_tracked_at, 'can_cancel' => $shipment->canCancel(), 'is_delivered' => $shipment->isDelivered(), 'email' => $shipment->email, // E-Mail für Tracking-E-Mail Button 'can_send_email' => $shipment->canSendTrackingEmail(), ]; })->toArray(); } /** * Calculate order weight in kg */ private function calculateOrderWeight(ShoppingOrder $order): float { return (new DhlShipmentWeightCalculator)->calculate($order); } /** * Process and parse shipping address from order */ private function processShippingAddress(ShoppingOrder $order): array { $shoppingUser = $order->shopping_user; if (! $shoppingUser) { return $this->getEmptyAddress(); } // Determine if shipping address is different from billing $useShipping = ! ($shoppingUser->same_as_billing ?? true); // Extract address data $addressData = [ 'firstname' => $useShipping ? ($shoppingUser->shipping_firstname ?? '') : ($shoppingUser->billing_firstname ?? ''), 'lastname' => $useShipping ? ($shoppingUser->shipping_lastname ?? '') : ($shoppingUser->billing_lastname ?? ''), 'company' => $useShipping ? ($shoppingUser->shipping_company ?? '') : ($shoppingUser->billing_company ?? ''), 'address' => $useShipping ? ($shoppingUser->shipping_address ?? '') : ($shoppingUser->billing_address ?? ''), 'address_2' => $useShipping ? ($shoppingUser->shipping_address_2 ?? '') : ($shoppingUser->billing_address_2 ?? ''), 'zipcode' => $useShipping ? ($shoppingUser->shipping_zipcode ?? '') : ($shoppingUser->billing_zipcode ?? ''), 'city' => $useShipping ? ($shoppingUser->shipping_city ?? '') : ($shoppingUser->billing_city ?? ''), 'country' => $useShipping ? ($shoppingUser->shipping_country ?? null) : ($shoppingUser->billing_country ?? null), 'phone' => $useShipping ? ($shoppingUser->shipping_phone ?? '') : ($shoppingUser->billing_phone ?? ''), 'email' => $shoppingUser->billing_email ?? '', 'houseNumber' => '', // DHL Postnummer für Packstation/Paketbox (nur bei Versandadresse) 'postnumber' => $useShipping ? ($shoppingUser->shipping_postnumber ?? '') : '', ]; // Parse and separate street name and number $this->parseStreetAddress($addressData); return $addressData; } /** * Parse street address and separate street name from house number */ private function parseStreetAddress(array &$addressData): void { $address = trim($addressData['address']); // If address_2 is empty and address contains both street and number if (! empty($address)) { // Try to separate street name and house number $patterns = [ // Pattern 1: "Musterstraße 123" or "Musterstraße 123a" '/^(.+?)\s+(\d+[a-zA-Z]?)$/u', // Pattern 2: "Musterstraße 123-125" or "Musterstraße 123/125" '/^(.+?)\s+(\d+[-\/]\d+[a-zA-Z]?)$/u', // Pattern 3: "123 Musterstraße" (number first) '/^(\d+[a-zA-Z]?)\s+(.+)$/u', ]; foreach ($patterns as $index => $pattern) { if (preg_match($pattern, $address, $matches)) { if ($index === 2) { // Number first pattern $addressData['address'] = trim($matches[2]); $addressData['houseNumber'] = trim($matches[1]); } else { // Street first patterns $addressData['address'] = trim($matches[1]); $addressData['houseNumber'] = trim($matches[2]); } break; } } } // Clean up the address data $addressData['address'] = trim($addressData['address']); $addressData['houseNumber'] = trim($addressData['houseNumber']); } /** * Validate address completeness and format * * @return array Validation result with 'valid', 'errors', and 'warnings' keys */ private function validateAddress(array $address): array { $result = (new DhlAddressValidator)->validate($address); return [ 'valid' => $result['can_create_label'], 'errors' => $result['errors'], 'warnings' => $result['warnings'], ]; } /** * Get empty address template */ private function getEmptyAddress(): array { return [ 'firstname' => '', 'lastname' => '', 'company' => '', 'address' => '', 'address_2' => '', 'zipcode' => '', 'city' => '', 'country' => null, 'phone' => '', 'email' => '', 'postnumber' => '', ]; } /** * Get available countries for shipping * * @return \Illuminate\Database\Eloquent\Collection */ private function getAvailableCountries() { return Country::where('active', 1)->get(); } /** * Get available DHL product codes from settings */ private function getAvailableProductCodes(): array { // Get DHL configuration with merged settings $settingController = new \App\Http\Controllers\SettingController; $dhlConfig = $settingController->getDhlConfig(); $productCodes = []; // Add products based on configured account numbers $accountNumbers = $dhlConfig['account_numbers'] ?? []; if (! empty($accountNumbers['V01PAK'])) { $productCodes['V01PAK'] = 'DHL Paket National'; } if (! empty($accountNumbers['V53PAK'])) { $productCodes['V53PAK'] = 'DHL Paket International'; } if (! empty($accountNumbers['V62KP'])) { $productCodes['V62KP'] = 'DHL Kleinpaket'; } if (! empty($accountNumbers['V07PAK'])) { $productCodes['V07PAK'] = 'DHL Retoure Online'; } // Fallback to default if no account numbers configured if (empty($productCodes)) { $productCodes = [ 'V01PAK' => 'DHL Paket National', 'V53PAK' => 'DHL Paket International', 'V62KP' => 'DHL Kleinpaket', ]; } return $productCodes; } private function getSuggestedProductCode(array $shippingAddress): string { $countryCode = $shippingAddress['country']?->code; if (! $countryCode) { return 'V01PAK'; } try { return (new DhlProductResolver)->resolveProductCode($countryCode, null, 'V01PAK'); } catch (\InvalidArgumentException) { return 'V01PAK'; } } /** * Validate shipment parameters before API call * * @return array Validation result */ public function validateShipmentData(array $shipmentData): array { $errors = []; $warnings = []; // Weight validation $weight = floatval($shipmentData['weight'] ?? 0); if ($weight < 0.1) { $errors[] = 'Gewicht muss mindestens 0.1 kg betragen.'; } elseif ($weight > 31.5) { $errors[] = 'Gewicht darf maximal 31.5 kg betragen.'; } // Product code validation $productCode = $shipmentData['product_code'] ?? ''; $availableProducts = array_keys($this->getAvailableProductCodes()); if (! in_array($productCode, $availableProducts)) { $errors[] = 'Ungültiger Produktcode ausgewählt.'; } if (! empty($shipmentData['shipping_country_id']) && $productCode) { $country = Country::find($shipmentData['shipping_country_id']); if ($country) { try { (new DhlProductResolver)->resolveProductCode($country->code, $productCode); } catch (\InvalidArgumentException $e) { $errors[] = $e->getMessage(); } } } if ($productCode) { try { (new DhlShipmentWeightCalculator)->assertWithinProductLimit($weight, $productCode); } catch (\InvalidArgumentException $e) { $errors[] = $e->getMessage(); } } $country = null; if (! empty($shipmentData['shipping_country_id'])) { $country = Country::find($shipmentData['shipping_country_id']); } $addressValidation = (new DhlAddressValidator)->validate(array_merge($shipmentData, [ 'shipping_country_code' => $country?->code, ])); $errors = array_merge($errors, $addressValidation['errors']); $warnings = array_merge($warnings, $addressValidation['warnings']); return [ 'valid' => empty($errors), 'errors' => $errors, 'warnings' => $warnings, ]; } /** * Prepare address data for DHL API */ public function prepareAddressForApi(array $formData): array { $country = null; if (! empty($formData['shipping_country_id'])) { $country = Country::find($formData['shipping_country_id']); } return [ 'firstname' => trim($formData['shipping_firstname'] ?? ''), 'lastname' => trim($formData['shipping_lastname'] ?? ''), 'company' => trim($formData['shipping_company'] ?? ''), 'address' => trim($formData['shipping_address'] ?? ''), 'address_2' => trim($formData['shipping_address_2'] ?? ''), 'houseNumber' => trim($formData['shipping_houseNumber'] ?? ''), 'zipcode' => trim($formData['shipping_zipcode'] ?? ''), 'city' => trim($formData['shipping_city'] ?? ''), 'country_id' => $country?->id, 'country' => $country, // Store country object for DhlDataHelper 'phone' => trim($formData['shipping_phone'] ?? ''), 'email' => trim($formData['shipping_email'] ?? ''), // Add email if available 'postnumber' => trim($formData['shipping_postnumber'] ?? ''), // DHL Postnummer für Packstation/Paketbox ]; } }