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(), 'errors' => [], 'warnings' => [] ]; // 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; // Calculate order weight $result['orderWeight'] = $this->calculateOrderWeight($order); // Process and validate shipping address $result['shippingAddress'] = $this->processShippingAddress($order); // 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 successfully', [ '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 * @return ShoppingOrder|null */ private function loadOrder($id): ?ShoppingOrder { return ShoppingOrder::with([ 'shopping_order_items', 'shopping_user', ])->find($id); } /** * Calculate order weight in kg * * @param ShoppingOrder $order * @return float */ private function calculateOrderWeight(ShoppingOrder $order): float { return $order->weight / 100; /* // Default fallback weight $defaultWeight = 1.0; if (!$order->shopping_order_items || $order->shopping_order_items->isEmpty()) { return $defaultWeight; } // If order has a weight field (in grams), convert to kg if ($order->weight && $order->weight > 0) { return round($order->weight / 100, 1); // Convert grams to kg } // Calculate from items if available $totalWeight = 0; foreach ($order->shopping_order_items as $item) { if ($item->weight && $item->weight > 0) { $totalWeight += ($item->weight * $item->quantity); } } if ($totalWeight > 0) { return round($totalWeight / 100, 1); // Convert grams to kg } // Estimate based on item count if no weight data $itemCount = $order->shopping_order_items->sum('quantity'); $estimatedWeight = max($itemCount * 0.5, $defaultWeight); // Estimate 0.5kg per item return round($estimatedWeight, 1); */ } /** * Process and parse shipping address from order * * @param ShoppingOrder $order * @return array */ 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' => '', ]; // Parse and separate street name and number $this->parseStreetAddress($addressData); return $addressData; } /** * Parse street address and separate street name from house number * * @param array &$addressData */ 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 * * @param array $address * @return array Validation result with 'valid', 'errors', and 'warnings' keys */ private function validateAddress(array $address): array { $errors = []; $warnings = []; // Required fields $requiredFields = [ 'firstname' => 'Vorname', 'lastname' => 'Nachname', 'address' => 'Straße', 'zipcode' => 'Postleitzahl', 'city' => 'Stadt' ]; foreach ($requiredFields as $field => $label) { if (empty(trim($address[$field]))) { $errors[] = "{$label} ist erforderlich."; } } // Name validation if (empty(trim($address['firstname'])) && empty(trim($address['lastname'])) && empty(trim($address['company']))) { $errors[] = 'Entweder Name oder Firmenname muss angegeben werden.'; } // Street number validation if (!empty($address['address']) && empty($address['houseNumber'])) { $warnings[] = 'Hausnummer konnte nicht automatisch erkannt werden. Bitte prüfen Sie die Adressangaben.'; } // Postal code format validation for Germany if (!empty($address['zipcode']) && $address['country'] && $address['country']->code === 'DE') { if (!preg_match('/^\d{5}$/', $address['zipcode'])) { $warnings[] = 'Deutsche Postleitzahl sollte 5 Ziffern haben.'; } } // Country validation if (!$address['country']) { $errors[] = 'Land konnte nicht ermittelt werden.'; } return [ 'valid' => empty($errors), 'errors' => $errors, 'warnings' => $warnings ]; } /** * Get empty address template * * @return array */ private function getEmptyAddress(): array { return [ 'firstname' => '', 'lastname' => '', 'company' => '', 'address' => '', 'address_2' => '', 'zipcode' => '', 'city' => '', 'country' => null, 'phone' => '', 'email' => '', ]; } /** * 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 * * @return array */ 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['V62WP'])) { $productCodes['V62WP'] = 'DHL Warenpost National'; } 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', 'V62WP' => 'DHL Warenpost National' ]; } return $productCodes; } /** * Validate shipment parameters before API call * * @param array $shipmentData * @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.'; } // Address validation $requiredAddressFields = [ 'shipping_firstname' => 'Vorname', 'shipping_lastname' => 'Nachname', 'shipping_address' => 'Straße', 'shipping_houseNumber' => 'Hausnummer', 'shipping_zipcode' => 'Postleitzahl', 'shipping_city' => 'Stadt', 'shipping_country_id' => 'Land' ]; foreach ($requiredAddressFields as $field => $label) { if (empty(trim($shipmentData[$field] ?? ''))) { $errors[] = "{$label} ist erforderlich."; } } return [ 'valid' => empty($errors), 'errors' => $errors, 'warnings' => $warnings ]; } /** * Prepare address data for DHL API * * @param array $formData * @return array */ 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, 'phone' => trim($formData['shipping_phone'] ?? '') ]; } }