mivita/app/Services/DhlModalService.php

444 lines
16 KiB
PHP

<?php
namespace App\Services;
use App\Models\Country;
use App\Models\ShoppingOrder;
use Exception;
use Illuminate\Support\Facades\Log;
/**
* 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(),
'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
];
}
}