439 lines
14 KiB
PHP
439 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\ShoppingOrder;
|
|
use App\Models\Country;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Exception;
|
|
|
|
/**
|
|
* DHL Modal Service
|
|
*
|
|
* Service class that handles all business logic for the DHL shipment creation modal.
|
|
* Validates order data, processes addresses, and prepares data for the view.
|
|
*/
|
|
class DhlModalService
|
|
{
|
|
/**
|
|
* @var array DHL configuration
|
|
*/
|
|
private $config;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->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'] ?? '')
|
|
];
|
|
}
|
|
}
|