DHL Modul v0.5 Shipping Label ok
This commit is contained in:
parent
480fdc65ed
commit
8fdaa0ba1d
122 changed files with 17938 additions and 2239 deletions
213
app/Console/Commands/CheckPaymentsAccount.php
Normal file
213
app/Console/Commands/CheckPaymentsAccount.php
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\User;
|
||||
use App\Services\Util;
|
||||
use App\Models\UserHistory;
|
||||
use App\Models\UserMessage;
|
||||
use App\Mail\MailCustomMessage;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
||||
class CheckPaymentsAccount extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'payments:check-accounts';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Überprüft Benutzer-Zahlungskonten und sendet Erinnerungen basierend auf Erneuerungsdaten.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Log::channel('cron')->info('COMMAND [payments:check-accounts] started.');
|
||||
$this->info('COMMAND [payments:check-accounts] started.');
|
||||
|
||||
// Die Logik wurde 1:1 aus der checkPaymentsAccounts-Methode übernommen
|
||||
$renewalDate = Carbon::now()->modify('+'.(config('mivita.remind_first_days')+1).' days');
|
||||
Log::channel('cron')->info('Erneuerungsdatum für Zahlungen: ' . $renewalDate->format('Y-m-d H:i:s'));
|
||||
|
||||
$users = User::where('payment_account', '!=', NULL)
|
||||
->where('active', '=', 1)
|
||||
->where('blocked', '!=', 1)
|
||||
->where('payment_account', '<', $renewalDate)
|
||||
->get();
|
||||
|
||||
Log::channel('cron')->info('Found ' . $users->count() . ' users for payment reminders.');
|
||||
$this->info('Found ' . $users->count() . ' users for payment reminders.');
|
||||
|
||||
foreach ($users as $user){
|
||||
Log::channel('cron')->info('Prüfe Zahlungserinnerungen für Benutzer: ' . $user->email);
|
||||
$this->checkReminderPayments($user);
|
||||
}
|
||||
|
||||
Log::channel('cron')->info('COMMAND [payments:check-accounts] finished.');
|
||||
$this->info('COMMAND [payments:check-accounts] finished.');
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Überprüft und sendet Zahlungserinnerungen basierend auf Benutzerkontostand
|
||||
*
|
||||
* RULES:
|
||||
* > 21 remind_first_days = 31 reminder_first
|
||||
* > 21 remind_first_days + sepa = 32 reminder_first_sepa
|
||||
* > 14 remind_sec_days = 33 reminder_sec
|
||||
* > 2 remind_last_days = 34 reminder_last
|
||||
* > 0 deaktiv = 35 reminder_deaktiv
|
||||
* > 0 deaktiv + sepa = 36 reminder_deaktiv_sepa
|
||||
* == 7 abo_booking_days + sepa + cron = 37 reminder_collect_sepa
|
||||
*
|
||||
* @param User $user Benutzer
|
||||
* @return void
|
||||
*/
|
||||
|
||||
private function checkReminderPayments(User $user)
|
||||
{
|
||||
//35 reminder_deaktiv, 36 reminder_deaktiv_sepa
|
||||
if(!$user->isActiveAccount()){
|
||||
Log::channel('cron')->info('Inaktives Konto für Benutzer: ' . $user->email);
|
||||
$this->checkIsReminderSend($user, 35);
|
||||
return;
|
||||
}
|
||||
|
||||
//34 reminder_last
|
||||
if($user->daysActiveAccount() <= config('mivita.remind_last_days')){
|
||||
Log::channel('cron')->info('Letzte Erinnerung für Benutzer: ' . $user->email . ' (Tage aktiv: ' . $user->daysActiveAccount() . ')');
|
||||
$this->checkIsReminderSend($user, 34);
|
||||
return;
|
||||
}
|
||||
|
||||
//33 reminder_sec
|
||||
if($user->daysActiveAccount() <= config('mivita.remind_sec_days')){
|
||||
Log::channel('cron')->info('Zweite Erinnerung für Benutzer: ' . $user->email . ' (Tage aktiv: ' . $user->daysActiveAccount() . ')');
|
||||
$this->checkIsReminderSend($user, 33);
|
||||
return;
|
||||
}
|
||||
|
||||
//31 reminder_first
|
||||
if($user->daysActiveAccount() > config('mivita.remind_sec_days')){
|
||||
Log::channel('cron')->info('Erste Erinnerung für Benutzer: ' . $user->email . ' (Tage aktiv: ' . $user->daysActiveAccount() . ')');
|
||||
$this->checkIsReminderSend($user, 31);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Überprüft, ob eine Erinnerung bereits gesendet wurde
|
||||
*
|
||||
* @param User $user Benutzer
|
||||
* @param int $status Status-Code der Erinnerung
|
||||
* @return bool
|
||||
*/
|
||||
private function checkIsReminderSend(User $user, $status)
|
||||
{
|
||||
$isSend = UserHistory::whereUserId($user->id)
|
||||
->whereAction('reminder_payments')
|
||||
->whereIdentifier($user->payment_account)
|
||||
->whereStatus($status)
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if($isSend){
|
||||
Log::channel('cron')->info('Erinnerung bereits gesendet für Benutzer: ' . $user->email . ' (Status: ' . $status . ')');
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::channel('cron')->info('Sende neue Erinnerung für Benutzer: ' . $user->email . ' (Status: ' . $status . ')');
|
||||
$referenz = $this->sendReminderMail($user, $status);
|
||||
|
||||
UserHistory::create([
|
||||
'user_id' => $user->id,
|
||||
'action' => 'reminder_payments',
|
||||
'referenz' => $referenz,
|
||||
'identifier' => $user->payment_account,
|
||||
'status' => $status
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet eine Erinnerungs-E-Mail an den Benutzer
|
||||
*
|
||||
* @param User $user Benutzer
|
||||
* @param int $status Status-Code der Erinnerung
|
||||
* @return int
|
||||
*/
|
||||
private function sendReminderMail(User $user, $status)
|
||||
{
|
||||
$days = abs($user->daysActiveAccount());
|
||||
$pay_date = Carbon::parse($user->payment_account)->modify('- ' . config('mivita.abo_booking_days') . ' days')->format('d.m.Y');
|
||||
$datetime = $user->getPaymentAccountDateFormat();
|
||||
$price = "";
|
||||
|
||||
if($user->payment_order_id && isset($user->payment_order_product->price)){
|
||||
$price = 'von ' . $user->payment_order_product->getFormattedPrice() . ' EUR';
|
||||
}
|
||||
|
||||
$message = __('reminder.copy_first_' . $status, ['days' => $days, 'datetime' => $datetime, 'price' => $price, 'pay_date' => $pay_date]);
|
||||
$message_last = __('reminder.copy_last_' . $status, ['days' => $days, 'datetime' => $datetime, 'price' => $price, 'pay_date' => $pay_date]);
|
||||
$button = __('reminder.button_' . $status);
|
||||
|
||||
$message = preg_replace("/[\n\r]/", "", $message);
|
||||
$message_last = preg_replace("/[\n\r]/", "", $message_last);
|
||||
|
||||
$data = [
|
||||
'subject' => __('reminder.subject') . " | ID: " . $status,
|
||||
'message' => $message,
|
||||
'message_last' => $message_last,
|
||||
'url' => route('user_membership'),
|
||||
'button' => $button,
|
||||
];
|
||||
|
||||
$sender = User::find(1);
|
||||
$customer_mail = UserMessage::create(['user_id' => $user->id, 'send_user_id' => $sender->id, 'email' => $user->email, 'subject' => $data['subject'], 'message' => $data['message'] . " " . $data['message_last']]);
|
||||
|
||||
try {
|
||||
if(!Util::isTestSystem()){
|
||||
if($status >= 34){
|
||||
Log::channel('cron')->info('Sende kritische Erinnerung mit BCC an: ' . $user->email);
|
||||
Mail::to($user->email)
|
||||
->locale($user->getLocale())
|
||||
->bcc(config('app.default_mail'))
|
||||
->send(new MailCustomMessage($user, $data, $sender, false));
|
||||
} else {
|
||||
Log::channel('cron')->info('Sende normale Erinnerung an: ' . $user->email);
|
||||
Mail::to($user->email)
|
||||
->locale($user->getLocale())
|
||||
->send(new MailCustomMessage($user, $data, $sender, false));
|
||||
}
|
||||
} else {
|
||||
Log::channel('cron')->info('Testsystem: E-Mail-Versand simuliert für: ' . $user->email);
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
Log::channel('cron')->error('Mail-Fehler für Benutzer ' . $user->email . ': ' . $e->getMessage());
|
||||
$customer_mail->fail = true;
|
||||
$customer_mail->error = $e->getMessage();
|
||||
$customer_mail->save();
|
||||
return 0;
|
||||
}
|
||||
|
||||
$customer_mail->send = true;
|
||||
$customer_mail->sent_at = now();
|
||||
$customer_mail->save();
|
||||
|
||||
Log::channel('cron')->info('Erinnerungsmail erfolgreich gesendet an: ' . $user->email);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\BusinessStore;
|
||||
use App\Console\Commands\CheckPaymentsAccount;
|
||||
use App\Console\Commands\UserMakeAboOrder;
|
||||
use App\Console\Commands\UserCleanup;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
|
|
@ -15,6 +18,9 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected $commands = [
|
||||
BusinessStore::class,
|
||||
CheckPaymentsAccount::class,
|
||||
UserMakeAboOrder::class,
|
||||
UserCleanup::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -25,8 +31,15 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// $schedule->command('inspire')
|
||||
// ->hourly();
|
||||
// Job 1: Überprüft täglich um 02:00 Uhr die Zahlungskonten.
|
||||
$schedule->command('payments:check-accounts')->dailyAt('02:00');
|
||||
|
||||
// Jobs 2, 3, 4: Die Befehle aus deinem alten Shell-Skript.
|
||||
// Werden nacheinander täglich zu unterschiedlichen Zeiten ausgeführt,
|
||||
// um die Serverlast zu verteilen.
|
||||
$schedule->command('business:store 0 0')->dailyAt('03:00');
|
||||
$schedule->command('user:cleanup')->dailyAt('03:30');
|
||||
$schedule->command('user:make_abo_order')->dailyAt('04:00');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
662
app/Http/Controllers/DhlShipmentController.php
Normal file
662
app/Http/Controllers/DhlShipmentController.php
Normal file
|
|
@ -0,0 +1,662 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\CancelShipmentJob;
|
||||
use App\Jobs\CreateReturnLabelJob;
|
||||
use App\Jobs\TrackShipmentJob;
|
||||
// Old DHL model replaced with new package model
|
||||
use Acme\Dhl\Models\DhlShipment;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Services\DhlModalService;
|
||||
use App\Services\DhlShipmentService;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Yajra\DataTables\Facades\DataTables;
|
||||
|
||||
// Import new DHL package and SettingController
|
||||
use Acme\Dhl\DhlManager;
|
||||
|
||||
/**
|
||||
* DHL Shipment Controller
|
||||
*
|
||||
* Handles all DHL shipment operations including creation, cancellation,
|
||||
* tracking, and return labels. Provides both web interface and AJAX endpoints.
|
||||
*/
|
||||
class DhlShipmentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->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 '<label class="custom-control custom-checkbox mb-0"><input type="checkbox" class="custom-control-input shipment-checkbox" value="' . $shipment->id . '"><span class="custom-control-label"></span></label>';
|
||||
})
|
||||
->editColumn('id', function ($shipment) {
|
||||
return '<a href="' . route('admin.dhl.show', $shipment) . '" class="text-primary font-weight-semibold">#' . $shipment->id . '</a>';
|
||||
})
|
||||
->addColumn('type', function ($shipment) {
|
||||
if ($shipment->type == 'outbound') {
|
||||
return '<span class="badge badge-primary"><i class="fas fa-arrow-right"></i> Ausgehend</span>';
|
||||
} else {
|
||||
return '<span class="badge badge-info"><i class="fas fa-undo"></i> Retoure</span>';
|
||||
}
|
||||
})
|
||||
->addColumn('order', function ($shipment) {
|
||||
if ($shipment->order_id) {
|
||||
return '<a href="' . route('admin_sales_customers_detail', $shipment->order_id) . '" class="text-primary">#' . $shipment->order_id . '</a>';
|
||||
}
|
||||
return '<span class="text-muted">N/A</span>';
|
||||
})
|
||||
->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) .
|
||||
'<br><small class="text-muted">' . e($shipment->shoppingOrder->shopping_user->billing_email) . '</small>';
|
||||
}
|
||||
return '<span class="text-muted">Unbekannt</span>';
|
||||
})
|
||||
->editColumn('dhl_shipment_no', function ($shipment) {
|
||||
return $shipment->dhl_shipment_no ? '<code class="text-success">' . e($shipment->dhl_shipment_no) . '</code>' : '<span class="text-muted">-</span>';
|
||||
})
|
||||
->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 '<span class="badge badge-' . $statusInfo['class'] . '">' . $statusInfo['text'] . '</span>';
|
||||
})
|
||||
->addColumn('tracking_status', function ($shipment) {
|
||||
if ($shipment->tracking_status) {
|
||||
return '<small class="text-muted">' . e($shipment->tracking_status) . '</small>' .
|
||||
($shipment->last_tracked_at ? '<br><small class="text-muted">' . $shipment->last_tracked_at->format('d.m.Y H:i') . '</small>' : '');
|
||||
}
|
||||
return '<span class="text-muted">-</span>';
|
||||
})
|
||||
->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 = '<div class="btn-group" role="group">';
|
||||
$buttons .= '<a href="' . route('admin.dhl.show', $shipment) . '" class="btn btn-sm btn-outline-primary" data-toggle="tooltip" title="Details anzeigen"><i class="fas fa-eye"></i></a>';
|
||||
if ($shipment->label_path) {
|
||||
$buttons .= '<a href="' . route('admin.dhl.download-label', $shipment) . '" class="btn btn-sm btn-outline-success" data-toggle="tooltip" title="Label herunterladen"><i class="fas fa-download"></i></a>';
|
||||
}
|
||||
if ($shipment->canCancel()) {
|
||||
$buttons .= '<button type="button" class="btn btn-sm btn-outline-warning cancel-shipment-btn" data-shipment-id="' . $shipment->id . '" data-toggle="tooltip" title="Sendung stornieren"><i class="fas fa-ban"></i></button>';
|
||||
}
|
||||
if ($shipment->type == 'outbound' && !$shipment->returns()->count()) {
|
||||
$buttons .= '<button type="button" class="btn btn-sm btn-outline-info create-return-btn" data-shipment-id="' . $shipment->id . '" data-toggle="tooltip" title="Retourenlabel erstellen"><i class="fas fa-undo"></i></button>';
|
||||
}
|
||||
$buttons .= '</div>';
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
|
@ -130,24 +130,23 @@ class FileController extends Controller
|
|||
if(!Storage::disk($disk)->exists($path)){
|
||||
return Response::make('Datei nicht gefunden.', 404);
|
||||
}
|
||||
|
||||
if ($do === 'download') {
|
||||
return Storage::disk($disk)->download($path, $filename);
|
||||
}
|
||||
|
||||
$file = Storage::disk($disk)->get($path);
|
||||
$mime = Storage::disk($disk)->mimeType($path);
|
||||
|
||||
if(isset($file)){
|
||||
if($do === 'download'){
|
||||
return Response::make($file, 200)
|
||||
->header("Content-Type", $mime)
|
||||
->header('Content-disposition', 'attachment; filename="'.$filename.'"');
|
||||
}
|
||||
if($do === 'stream'){
|
||||
return Response::make($file, 200)
|
||||
->header("Content-Type", $mime)
|
||||
->header('Content-disposition','inline; filename="'.$filename.'"');
|
||||
return Storage::disk($disk)->response($path, $filename);
|
||||
}
|
||||
|
||||
if($do === 'file'){
|
||||
return Response::make($file, 200)
|
||||
->header("Content-Type", $mime)
|
||||
->header("Content-Length", strlen($file))
|
||||
->header('Content-disposition', 'filename="'.$filename.'"');
|
||||
}
|
||||
if($do === 'image'){
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use App\Models\ShoppingOrder;
|
|||
use App\Models\UserSalesVolume;
|
||||
use App\Services\BusinessPlan\TreeCalcBot;
|
||||
use App\Services\BusinessPlan\TreeCalcBotOptimized;
|
||||
use App\Services\DhlModalService;
|
||||
|
||||
class ModalController extends Controller
|
||||
{
|
||||
|
|
@ -170,6 +171,11 @@ class ModalController extends Controller
|
|||
$user_abo = UserAbo::find($data['id']);
|
||||
$ret = view("user.abo.modal_abo_show_products", compact( 'data', 'user_abo'))->render();
|
||||
}
|
||||
|
||||
if($data['action'] === 'create-dhl-shipment') {
|
||||
$id = $data['id'] ?? null;
|
||||
$ret = $this->handleDhlShipmentModal($id, $data);
|
||||
}
|
||||
|
||||
}
|
||||
return response()->json(['response' => $data, 'html'=>$ret, 'status'=>$status]);
|
||||
|
|
@ -195,6 +201,55 @@ class ModalController extends Controller
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle DHL shipment modal preparation
|
||||
*
|
||||
* @param mixed $id Order ID or 'new'
|
||||
* @param array $data Request data
|
||||
* @return string Rendered view
|
||||
*/
|
||||
private function handleDhlShipmentModal($id, array $data): string
|
||||
{
|
||||
try {
|
||||
$dhlModalService = new DhlModalService();
|
||||
$modalData = $dhlModalService->prepareModalData($id, $data);
|
||||
|
||||
// Merge the prepared data with the original request data
|
||||
$viewData = array_merge($data, $modalData, [
|
||||
'id' => $id,
|
||||
'data' => $data
|
||||
]);
|
||||
|
||||
return view("admin.dhl.modal_create_shipment", $viewData)->render();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('[ModalController] Error in DHL shipment modal', [
|
||||
'order_id' => $id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
// Return error view or fallback
|
||||
$errorData = [
|
||||
'id' => $id,
|
||||
'data' => $data,
|
||||
'order' => null,
|
||||
'orderWeight' => 1.0,
|
||||
'shippingAddress' => null,
|
||||
'availableCountries' => \App\Models\Country::where('active', 1)->get(),
|
||||
'productCodes' => [
|
||||
'V01PAK' => 'DHL Paket (National)',
|
||||
'V53WPAK' => 'DHL Paket International',
|
||||
'V54EPAK' => 'DHL Express'
|
||||
],
|
||||
'errors' => ['Fehler beim Laden der Daten: ' . $e->getMessage()],
|
||||
'warnings' => []
|
||||
];
|
||||
|
||||
return view("admin.dhl.modal_create_shipment", $errorData)->render();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,16 +65,16 @@ class InController extends Controller
|
|||
|
||||
$data = Request::all();
|
||||
|
||||
|
||||
$response = "";
|
||||
$status = false;
|
||||
|
||||
if(isset($data['action']) && $data['action'] === 'user-order-show-product'){
|
||||
$product = Product::find($data['id']); //current user form order
|
||||
$ret = view("admin.modal.show_product", compact('product', 'data'))->render();
|
||||
return response()->json(['response' => $data, 'html'=>$ret, 'status'=>$status]);
|
||||
|
||||
if(isset($data['action'])){
|
||||
if($data['action'] === 'user-order-show-product'){
|
||||
$product = Product::find($data['id']); //current user form order
|
||||
$ret = view("admin.modal.show_product", compact('product', 'data'))->render();
|
||||
return response()->json(['response' => $data, 'html'=>$ret, 'status'=>$status]);
|
||||
}
|
||||
}
|
||||
|
||||
$data = Request::get('data');
|
||||
$target = Request::get('target');
|
||||
if($data === "data_protection"){
|
||||
|
|
|
|||
|
|
@ -27,19 +27,92 @@ class SettingController extends Controller
|
|||
|
||||
public function store()
|
||||
{
|
||||
|
||||
$data = Request::all();
|
||||
if(isset($data['action'])){
|
||||
if(isset($data['settings'])){
|
||||
foreach ($data['settings'] as $key=>$value){
|
||||
if (isset($data['action'])) {
|
||||
if (isset($data['settings'])) {
|
||||
foreach ($data['settings'] as $key => $value) {
|
||||
$value['val'] = isset($value['val']) ? $value['val'] : false;
|
||||
Setting::setContentBySlug($key, $value['val'], $value['type']);
|
||||
}
|
||||
}
|
||||
|
||||
// DHL-spezifische Behandlung
|
||||
if ($data['action'] === 'save_dhl') {
|
||||
$this->updateDhlConfigCache();
|
||||
\Session()->flash('alert-save-dhl', 'DHL Konfiguration erfolgreich gespeichert!');
|
||||
} else {
|
||||
\Session()->flash('alert-save', '1');
|
||||
}
|
||||
}
|
||||
\Session()->flash('alert-save', '1');
|
||||
|
||||
return redirect(route('admin_settings'));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DHL configuration merged from database settings and .env values
|
||||
* Database settings override .env values
|
||||
*/
|
||||
public function getDhlConfig()
|
||||
{
|
||||
return [
|
||||
// API Settings
|
||||
'base_url' => Setting::getContentBySlug('dhl_base_url') ?: config('dhl.base_url'),
|
||||
'api_key' => Setting::getContentBySlug('dhl_api_key') ?: config('dhl.api_key'),
|
||||
'username' => Setting::getContentBySlug('dhl_username') ?: config('dhl.username'),
|
||||
'password' => Setting::getContentBySlug('dhl_password') ?: config('dhl.password'),
|
||||
'billing_number' => Setting::getContentBySlug('dhl_billing_number') ?: config('dhl.billing_number'),
|
||||
|
||||
// Product Settings
|
||||
'default_product' => Setting::getContentBySlug('dhl_product') ?: config('dhl.default_product'),
|
||||
'label_format' => Setting::getContentBySlug('dhl_label_format') ?: config('dhl.label_format'),
|
||||
'print_format' => Setting::getContentBySlug('dhl_print_format') ?: config('dhl.print_format'),
|
||||
'retoure_print_format' => Setting::getContentBySlug('dhl_retoure_print_format') ?: config('dhl.retoure_print_format'),
|
||||
'use_queue' => Setting::getContentBySlug('dhl_use_queue') ?: config('dhl.use_queue'),
|
||||
|
||||
// Sender Address
|
||||
'sender' => [
|
||||
'company' => Setting::getContentBySlug('dhl_sender_company') ?: config('dhl.sender.company'),
|
||||
'name' => Setting::getContentBySlug('dhl_sender_name') ?: config('dhl.sender.name'),
|
||||
'street' => Setting::getContentBySlug('dhl_sender_street') ?: config('dhl.sender.street'),
|
||||
'houseNumber' => Setting::getContentBySlug('dhl_sender_house_number') ?: config('dhl.sender.houseNumber'),
|
||||
'postalCode' => Setting::getContentBySlug('dhl_sender_postal_code') ?: config('dhl.sender.postalCode'),
|
||||
'city' => Setting::getContentBySlug('dhl_sender_city') ?: config('dhl.sender.city'),
|
||||
'country' => Setting::getContentBySlug('dhl_sender_country') ?: config('dhl.sender.country'),
|
||||
'email' => Setting::getContentBySlug('dhl_sender_email') ?: config('dhl.sender.email'),
|
||||
'phone' => Setting::getContentBySlug('dhl_sender_phone') ?: config('dhl.sender.phone'),
|
||||
],
|
||||
|
||||
// Account Numbers
|
||||
'account_numbers' => [
|
||||
'V01PAK' => Setting::getContentBySlug('dhl_account_v01pak') ?: config('dhl.account_numbers.V01PAK'),
|
||||
'V62WP' => Setting::getContentBySlug('dhl_account_v62wp') ?: config('dhl.account_numbers.V62WP'),
|
||||
'V53PAK' => Setting::getContentBySlug('dhl_account_v53pak') ?: config('dhl.account_numbers.V53PAK'),
|
||||
'V07PAK' => Setting::getContentBySlug('dhl_account_v07pak') ?: config('dhl.account_numbers.V07PAK'),
|
||||
'default' => config('dhl.account_numbers.default'),
|
||||
],
|
||||
|
||||
// Static config values (webhook, profile, legacy)
|
||||
'profile' => config('dhl.profile'),
|
||||
'webhook' => config('dhl.webhook'),
|
||||
'legacy' => config('dhl.legacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update DHL configuration cache after saving settings
|
||||
*/
|
||||
private function updateDhlConfigCache()
|
||||
{
|
||||
// Clear config cache to force reload from database
|
||||
\Artisan::call('config:clear');
|
||||
|
||||
// Optional: Test DHL connection with new settings
|
||||
try {
|
||||
$dhlManager = app('Acme\Dhl\DhlManager');
|
||||
// You could add a connection test here if needed
|
||||
\Log::info('DHL configuration updated successfully');
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('DHL configuration update failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,6 +322,9 @@ class UserShopController extends Controller
|
|||
return redirect()->back()->withErrors($validator)->withInput(Request::all());
|
||||
}
|
||||
\Session()->flash('shop-name-error', 'check');
|
||||
if(Request::get('user_shop_id')){
|
||||
return back()->withInput(Request::all());
|
||||
}
|
||||
return redirect(route('user_shop'))->withInput(Request::all());
|
||||
}
|
||||
|
||||
|
|
@ -329,6 +332,8 @@ class UserShopController extends Controller
|
|||
|
||||
$rules = array(
|
||||
'user_shop_name' => ' required|alpha_dash|unique:user_shops,name|min:4|max:20|full_word_check',
|
||||
'user_shop_active' => 'accepted',
|
||||
|
||||
);
|
||||
Validator::extend('full_word_check', function ($attribute, $value, $parameters, $validator) {
|
||||
if(in_array($value, config('profanity.full_word_check'))){
|
||||
|
|
@ -337,41 +342,43 @@ class UserShopController extends Controller
|
|||
return true;
|
||||
});
|
||||
$validator = Validator::make(Request::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
\Session()->flash('shop-name-error', 'error');
|
||||
}else{
|
||||
\Session()->flash('shop-name-error', 'check');
|
||||
|
||||
}
|
||||
|
||||
$rules = array(
|
||||
'user_shop_active' => 'accepted',
|
||||
);
|
||||
$validator = Validator::make(Request::all(), $rules);
|
||||
if ($validator->fails()) {
|
||||
\Session()->flash('shop-name-error', 'error');
|
||||
return redirect()->back()->withErrors($validator)->withInput(Request::all());
|
||||
}
|
||||
\Session()->flash('shop-name-error', 'check');
|
||||
|
||||
//all is right - save
|
||||
$user = Auth::user();
|
||||
$data = Request::all();
|
||||
$slug = SlugService::createSlug(UserShop::class, 'slug', $data['user_shop_name']);
|
||||
$user_shop = UserShop::create([
|
||||
'user_id' => $user->id,
|
||||
'name' => $slug,
|
||||
'active' => true,
|
||||
'active_date' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
$ret = $this->userShopRegisterSubDomain($user_shop->slug);
|
||||
if(isset($data['user_shop_id'])){
|
||||
$user_shop = UserShop::find($data['user_shop_id']);
|
||||
if($user_shop->user_id != $user->id){
|
||||
abort(404);
|
||||
}
|
||||
$user_shop->name = $slug;
|
||||
$user_shop->slug = $slug;
|
||||
$user_shop->save();
|
||||
}else{
|
||||
$user_shop = UserShop::create([
|
||||
'user_id' => $user->id,
|
||||
'name' => $slug,
|
||||
'active' => true,
|
||||
'active_date' => now(),
|
||||
]
|
||||
);
|
||||
}
|
||||
\Session()->flash('alert-save', true);
|
||||
return redirect(route('user_shop'));
|
||||
/*$ret = $this->userShopRegisterSubDomain($user_shop->slug);
|
||||
if($ret['success'] === true){
|
||||
\Session()->flash('alert-save', true);
|
||||
}else{
|
||||
$user_shop->forceDelete();
|
||||
\Session()->flash('alert-error', $ret['error']);
|
||||
}
|
||||
return redirect(route('user_shop'));
|
||||
return redirect(route('user_shop'));*/
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -444,4 +451,19 @@ class UserShopController extends Controller
|
|||
));
|
||||
}
|
||||
|
||||
public function editName(){
|
||||
$user = Auth::user();
|
||||
$user_shop = $user->shop;
|
||||
if(!$user_shop){
|
||||
abort(404);
|
||||
}
|
||||
$user_shop_domain = $user_shop->getSubdomain(false);
|
||||
$data = [
|
||||
'user' => $user,
|
||||
'user_shop_id' => $user_shop->id,
|
||||
'user_shop_domain' => $user_shop_domain,
|
||||
];
|
||||
return view('user.shop_edit_name', $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -37,24 +37,26 @@ class DomainResolver
|
|||
// leiten wir sicher auf die Hauptdomain um.
|
||||
if ($context->isUnknown()) {
|
||||
// Detailliertes Logging für spätere Analyse
|
||||
\Log::warning('Unknown domain accessed', [
|
||||
'host' => $request->getHost(),
|
||||
'subdomain' => $context->subdomain,
|
||||
'user_agent' => $request->userAgent(),
|
||||
'ip' => $request->ip(),
|
||||
'referer' => $request->header('referer'),
|
||||
'path' => $request->getPathInfo()
|
||||
]);
|
||||
|
||||
if(config('app.debug')){
|
||||
\Log::warning('Unknown domain accessed', [
|
||||
'host' => $request->getHost(),
|
||||
'subdomain' => $context->subdomain,
|
||||
'user_agent' => $request->userAgent(),
|
||||
'ip' => $request->ip(),
|
||||
'referer' => $request->header('referer'),
|
||||
'path' => $request->getPathInfo()
|
||||
]);
|
||||
}
|
||||
// Holt die URL der Hauptdomain vom DomainService und leitet um.
|
||||
$mainUrl = app(\App\Services\DomainService::class)->buildUrl('main');
|
||||
return redirect()->away($mainUrl, 301);
|
||||
}
|
||||
\Log::debug('DomainResolver: context', [
|
||||
'context' => $context,
|
||||
'subdomain' => $context->subdomain
|
||||
]);
|
||||
|
||||
if(config('app.debug')){
|
||||
\Log::debug('DomainResolver: context', [
|
||||
'context' => $context,
|
||||
'subdomain' => $context->subdomain
|
||||
]);
|
||||
}
|
||||
// Für User-Shop-Domains: Validierung und Route-Parameter-Bereinigung
|
||||
if ($context->isUserShop()) {
|
||||
// Validiere UserShop-Berechtigung (bereits im DomainServiceProvider geprüft,
|
||||
|
|
|
|||
144
app/Jobs/CancelShipmentJob.php
Normal file
144
app/Jobs/CancelShipmentJob.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\DhlShipment;
|
||||
use App\Services\DhlApiService;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable as BusQueueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Job to cancel DHL shipments asynchronously
|
||||
*
|
||||
* This job handles the cancellation of DHL shipments in the background,
|
||||
* preventing API timeouts and improving user experience.
|
||||
*/
|
||||
class CancelShipmentJob implements ShouldQueue
|
||||
{
|
||||
use BusQueueable, Dispatchable, InteractsWithQueue, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var DhlShipment
|
||||
*/
|
||||
public $dhlShipment;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 3;
|
||||
|
||||
/**
|
||||
* The maximum number of seconds the job can run before timing out.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 60;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param DhlShipment $dhlShipment
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(DhlShipment $dhlShipment, array $options = [])
|
||||
{
|
||||
$this->dhlShipment = $dhlShipment;
|
||||
$this->options = $options;
|
||||
|
||||
// Set queue name based on priority
|
||||
if (isset($options['priority']) && $options['priority'] === 'high') {
|
||||
$this->onQueue('high-priority');
|
||||
} else {
|
||||
$this->onQueue('dhl-cancellations');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
Log::info('[DHL Queue] Starting shipment cancellation job', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'shipment_number' => $this->dhlShipment->shipment_number,
|
||||
'attempt' => $this->attempts(),
|
||||
]);
|
||||
|
||||
$dhlService = new DhlApiService();
|
||||
|
||||
// Cancel the shipment
|
||||
$success = $dhlService->cancelShipment($this->dhlShipment);
|
||||
|
||||
if ($success) {
|
||||
Log::info('[DHL Queue] Shipment cancelled successfully', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'shipment_number' => $this->dhlShipment->shipment_number,
|
||||
]);
|
||||
} else {
|
||||
throw new Exception('Cancellation returned false');
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error('[DHL Queue] Shipment cancellation failed', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'shipment_number' => $this->dhlShipment->shipment_number,
|
||||
'error' => $e->getMessage(),
|
||||
'attempt' => $this->attempts(),
|
||||
'max_tries' => $this->tries,
|
||||
]);
|
||||
|
||||
// If this is the final attempt, mark as permanently failed
|
||||
if ($this->attempts() >= $this->tries) {
|
||||
Log::error('[DHL Queue] Shipment cancellation permanently failed', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
throw $e; // Re-throw to trigger retry mechanism
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a job failure.
|
||||
*
|
||||
* @param Exception $exception
|
||||
*/
|
||||
public function failed(Exception $exception): void
|
||||
{
|
||||
Log::error('[DHL Queue] CancelShipmentJob permanently failed', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'shipment_number' => $this->dhlShipment->shipment_number,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
]);
|
||||
|
||||
// You could implement additional failure handling here:
|
||||
// - Send notification to admin
|
||||
// - Create manual task for staff to handle cancellation
|
||||
// - Update shipment status to indicate cancellation failed
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function retryUntil()
|
||||
{
|
||||
return now()->addHour(); // Shorter timeout for cancellations
|
||||
}
|
||||
}
|
||||
148
app/Jobs/CreateReturnLabelJob.php
Normal file
148
app/Jobs/CreateReturnLabelJob.php
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\DhlShipment;
|
||||
use App\Services\DhlApiService;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable as BusQueueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Job to create DHL return labels asynchronously
|
||||
*
|
||||
* This job handles the creation of DHL return labels in the background,
|
||||
* preventing API timeouts and improving user experience.
|
||||
*/
|
||||
class CreateReturnLabelJob implements ShouldQueue
|
||||
{
|
||||
use BusQueueable, Dispatchable, InteractsWithQueue, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var DhlShipment
|
||||
*/
|
||||
public $originalShipment;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 3;
|
||||
|
||||
/**
|
||||
* The maximum number of seconds the job can run before timing out.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 120;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param DhlShipment $originalShipment
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(DhlShipment $originalShipment, array $options = [])
|
||||
{
|
||||
$this->originalShipment = $originalShipment;
|
||||
$this->options = $options;
|
||||
|
||||
// Set queue name based on priority
|
||||
if (isset($options['priority']) && $options['priority'] === 'high') {
|
||||
$this->onQueue('high-priority');
|
||||
} else {
|
||||
$this->onQueue('dhl-returns');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
Log::info('[DHL Queue] Starting return label creation job', [
|
||||
'original_shipment_id' => $this->originalShipment->id,
|
||||
'original_shipment_number' => $this->originalShipment->shipment_number,
|
||||
'attempt' => $this->attempts(),
|
||||
]);
|
||||
|
||||
$dhlService = new DhlApiService();
|
||||
|
||||
// Create the return label
|
||||
$returnShipment = $dhlService->createReturnLabel(
|
||||
$this->originalShipment,
|
||||
$this->options
|
||||
);
|
||||
|
||||
Log::info('[DHL Queue] Return label created successfully', [
|
||||
'original_shipment_id' => $this->originalShipment->id,
|
||||
'return_shipment_id' => $returnShipment->id,
|
||||
'return_shipment_number' => $returnShipment->shipment_number,
|
||||
]);
|
||||
|
||||
// Trigger follow-up actions if specified
|
||||
if (isset($this->options['auto_track']) && $this->options['auto_track']) {
|
||||
\App\Jobs\TrackShipmentJob::dispatch($returnShipment)->delay(now()->addMinutes(5));
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error('[DHL Queue] Return label creation failed', [
|
||||
'original_shipment_id' => $this->originalShipment->id,
|
||||
'error' => $e->getMessage(),
|
||||
'attempt' => $this->attempts(),
|
||||
'max_tries' => $this->tries,
|
||||
]);
|
||||
|
||||
// If this is the final attempt, mark as permanently failed
|
||||
if ($this->attempts() >= $this->tries) {
|
||||
Log::error('[DHL Queue] Return label creation permanently failed', [
|
||||
'original_shipment_id' => $this->originalShipment->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
throw $e; // Re-throw to trigger retry mechanism
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a job failure.
|
||||
*
|
||||
* @param Exception $exception
|
||||
*/
|
||||
public function failed(Exception $exception): void
|
||||
{
|
||||
Log::error('[DHL Queue] CreateReturnLabelJob permanently failed', [
|
||||
'original_shipment_id' => $this->originalShipment->id,
|
||||
'original_shipment_number' => $this->originalShipment->shipment_number,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
]);
|
||||
|
||||
// You could implement additional failure handling here:
|
||||
// - Send notification to admin
|
||||
// - Create manual task for staff to handle return label creation
|
||||
// - Update original shipment to mark return label creation failed
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function retryUntil()
|
||||
{
|
||||
return now()->addHours(2);
|
||||
}
|
||||
}
|
||||
179
app/Jobs/CreateShipmentJob.php
Normal file
179
app/Jobs/CreateShipmentJob.php
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Services\DhlDataHelper;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable as BusQueueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Job to create DHL shipments asynchronously
|
||||
*
|
||||
* This job handles the creation of DHL shipments in the background,
|
||||
* preventing API timeouts and improving user experience.
|
||||
*/
|
||||
class CreateShipmentJob implements ShouldQueue
|
||||
{
|
||||
use BusQueueable, Dispatchable, InteractsWithQueue, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var ShoppingOrder
|
||||
*/
|
||||
public $shoppingOrder;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
public $weight;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $dhlConfig;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 3;
|
||||
|
||||
/**
|
||||
* The maximum number of seconds the job can run before timing out.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 120;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param ShoppingOrder $shoppingOrder
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
* @param array|null $dhlConfig
|
||||
*/
|
||||
public function __construct(ShoppingOrder $shoppingOrder, float $weight = 1.0, array $options = [], array $dhlConfig = [])
|
||||
{
|
||||
$this->shoppingOrder = $shoppingOrder;
|
||||
$this->weight = $weight;
|
||||
$this->options = $options;
|
||||
|
||||
// Load DHL config once when creating the job
|
||||
if (empty($dhlConfig)) {
|
||||
$settingController = new \App\Http\Controllers\SettingController();
|
||||
$this->dhlConfig = $settingController->getDhlConfig();
|
||||
} else {
|
||||
$this->dhlConfig = $dhlConfig;
|
||||
}
|
||||
|
||||
// Set queue name based on priority
|
||||
if (isset($options['priority']) && $options['priority'] === 'high') {
|
||||
$this->onQueue('high-priority');
|
||||
} else {
|
||||
$this->onQueue('dhl-shipments');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
Log::info('[DHL Queue] Starting shipment creation job', [
|
||||
'order_id' => $this->shoppingOrder->id,
|
||||
'weight' => $this->weight,
|
||||
'attempt' => $this->attempts(),
|
||||
]);
|
||||
|
||||
// Use DHL configuration loaded in constructor
|
||||
$dhlClient = new \Acme\Dhl\Support\DhlClient(
|
||||
$this->dhlConfig['base_url'],
|
||||
$this->dhlConfig['api_key'],
|
||||
$this->dhlConfig['username'],
|
||||
$this->dhlConfig['password']
|
||||
);
|
||||
|
||||
$shippingService = new \Acme\Dhl\Services\ShippingService($dhlClient);
|
||||
|
||||
// Prepare order data using helper
|
||||
$orderData = DhlDataHelper::prepareOrderData($this->shoppingOrder, $this->weight, $this->options, $this->dhlConfig);
|
||||
|
||||
// Create the shipment using new package
|
||||
$result = $shippingService->createLabel($orderData);
|
||||
|
||||
Log::info('[DHL Queue] Shipment created successfully', [
|
||||
'order_id' => $this->shoppingOrder->id,
|
||||
'shipment_number' => $result['shipmentNumber'] ?? 'N/A',
|
||||
'label_path' => $result['labelPath'] ?? 'N/A',
|
||||
]);
|
||||
|
||||
// Trigger follow-up actions if specified (if tracking number available)
|
||||
if (isset($this->options['auto_track']) && $this->options['auto_track'] && !empty($result['trackingNumber'])) {
|
||||
Log::info('[DHL Queue] Scheduling tracking update', [
|
||||
'tracking_number' => $result['trackingNumber']
|
||||
]);
|
||||
// Note: TrackShipmentJob would need to be updated to work with tracking numbers
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error('[DHL Queue] Shipment creation failed', [
|
||||
'order_id' => $this->shoppingOrder->id,
|
||||
'error' => $e->getMessage(),
|
||||
'attempt' => $this->attempts(),
|
||||
'max_tries' => $this->tries,
|
||||
]);
|
||||
|
||||
// If this is the final attempt, mark as permanently failed
|
||||
if ($this->attempts() >= $this->tries) {
|
||||
Log::error('[DHL Queue] Shipment creation permanently failed', [
|
||||
'order_id' => $this->shoppingOrder->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
throw $e; // Re-throw to trigger retry mechanism
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a job failure.
|
||||
*
|
||||
* @param Exception $exception
|
||||
*/
|
||||
public function failed(Exception $exception): void
|
||||
{
|
||||
Log::error('[DHL Queue] CreateShipmentJob permanently failed', [
|
||||
'order_id' => $this->shoppingOrder->id,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
]);
|
||||
|
||||
// You could implement additional failure handling here:
|
||||
// - Send notification to admin
|
||||
// - Update order status
|
||||
// - Create manual task for staff
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function retryUntil()
|
||||
{
|
||||
return now()->addHours(2);
|
||||
}
|
||||
}
|
||||
194
app/Jobs/TrackShipmentJob.php
Normal file
194
app/Jobs/TrackShipmentJob.php
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\DhlShipment;
|
||||
use App\Services\DhlApiService;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable as BusQueueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Job to track DHL shipments asynchronously
|
||||
*
|
||||
* This job handles the tracking of DHL shipments in the background,
|
||||
* updating tracking status and details automatically.
|
||||
*/
|
||||
class TrackShipmentJob implements ShouldQueue
|
||||
{
|
||||
use BusQueueable, Dispatchable, InteractsWithQueue, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var DhlShipment
|
||||
*/
|
||||
public $dhlShipment;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 2; // Lower tries for tracking as it's less critical
|
||||
|
||||
/**
|
||||
* The maximum number of seconds the job can run before timing out.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 60;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param DhlShipment $dhlShipment
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(DhlShipment $dhlShipment, array $options = [])
|
||||
{
|
||||
$this->dhlShipment = $dhlShipment;
|
||||
$this->options = $options;
|
||||
|
||||
// Set queue name - tracking is usually lower priority
|
||||
$this->onQueue('dhl-tracking');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
Log::info('[DHL Queue] Starting shipment tracking job', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'tracking_number' => $this->dhlShipment->tracking_number,
|
||||
'attempt' => $this->attempts(),
|
||||
]);
|
||||
|
||||
$dhlService = new DhlApiService();
|
||||
|
||||
// Get tracking details
|
||||
$trackingDetails = $dhlService->getTrackingDetails($this->dhlShipment);
|
||||
|
||||
Log::info('[DHL Queue] Shipment tracking updated successfully', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'tracking_status' => $trackingDetails['status'] ?? 'unknown',
|
||||
'events_count' => isset($trackingDetails['events']) ? count($trackingDetails['events']) : 0,
|
||||
]);
|
||||
|
||||
// Schedule next tracking update if shipment is still in transit
|
||||
if (isset($this->options['auto_retrack']) && $this->options['auto_retrack']) {
|
||||
$status = $trackingDetails['status'] ?? '';
|
||||
if ($this->shouldContinueTracking($status)) {
|
||||
// Schedule next tracking in 2-6 hours based on current status
|
||||
$nextTrackingDelay = $this->getNextTrackingDelay($status);
|
||||
TrackShipmentJob::dispatch($this->dhlShipment, $this->options)
|
||||
->delay(now()->addMinutes($nextTrackingDelay));
|
||||
|
||||
Log::info('[DHL Queue] Next tracking job scheduled', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'delay_minutes' => $nextTrackingDelay,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::warning('[DHL Queue] Shipment tracking failed', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'tracking_number' => $this->dhlShipment->tracking_number,
|
||||
'error' => $e->getMessage(),
|
||||
'attempt' => $this->attempts(),
|
||||
'max_tries' => $this->tries,
|
||||
]);
|
||||
|
||||
// For tracking, we don't necessarily need to fail hard
|
||||
if ($this->attempts() >= $this->tries) {
|
||||
Log::warning('[DHL Queue] Shipment tracking permanently failed', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
// Don't re-throw for final attempt - just log and continue
|
||||
return;
|
||||
}
|
||||
|
||||
throw $e; // Re-throw to trigger retry mechanism
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a job failure.
|
||||
*
|
||||
* @param Exception $exception
|
||||
*/
|
||||
public function failed(Exception $exception): void
|
||||
{
|
||||
Log::warning('[DHL Queue] TrackShipmentJob permanently failed', [
|
||||
'shipment_id' => $this->dhlShipment->id,
|
||||
'tracking_number' => $this->dhlShipment->tracking_number,
|
||||
'error' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
// Tracking failures are less critical - just log them
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we should continue tracking this shipment
|
||||
*
|
||||
* @param string $status
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldContinueTracking(string $status): bool
|
||||
{
|
||||
$finalStates = [
|
||||
'delivered',
|
||||
'delivered_to_recipient',
|
||||
'delivered_to_pickup_location',
|
||||
'returned_to_sender',
|
||||
'cancelled',
|
||||
'lost',
|
||||
];
|
||||
|
||||
return !in_array(strtolower($status), $finalStates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delay for next tracking update based on current status
|
||||
*
|
||||
* @param string $status
|
||||
* @return int Minutes until next tracking
|
||||
*/
|
||||
private function getNextTrackingDelay(string $status): int
|
||||
{
|
||||
switch (strtolower($status)) {
|
||||
case 'picked_up':
|
||||
case 'in_transit':
|
||||
return 120; // 2 hours for active shipments
|
||||
case 'out_for_delivery':
|
||||
return 60; // 1 hour when out for delivery
|
||||
case 'exception':
|
||||
case 'failed_attempt':
|
||||
return 240; // 4 hours for problem shipments
|
||||
default:
|
||||
return 180; // 3 hours default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function retryUntil()
|
||||
{
|
||||
return now()->addMinutes(30); // Short timeout for tracking
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ class MailAutoReleaseAccount extends Mailable
|
|||
$copy1line .= 'Sollte Daten nicht vollständig sein, bitte Kontakt zum Berater aufnehmen.'."\n";
|
||||
|
||||
return $this->view('emails.info')->with([
|
||||
'url' => route('admin_lead_edit', $this->user->id).'?show=check_lead',
|
||||
'url' => config('app.url_crm') . '/admin/lead/edit/' . $this->user->id . '?show=check_lead',
|
||||
'title' => 'Berater Registrierung prüfen',
|
||||
'button' => 'zur Berater Prüfung',
|
||||
'copy1line' => $copy1line,
|
||||
|
|
|
|||
|
|
@ -61,14 +61,14 @@ class MailInfo extends Mailable
|
|||
$copy1line = "Infos zum Berater:"."\n";
|
||||
$button = "zum Berater";
|
||||
$title = "Ein Berater möchte seine Mitgliedschaft beenden.";
|
||||
$url = route('admin_lead_edit', $this->user->id).'?show=check_lead';
|
||||
$url = config('app.url_crm') . '/admin/lead/edit/' . $this->user->id . '?show=check_lead';
|
||||
}
|
||||
|
||||
if($this->action === "check_is_like_customer") {
|
||||
$copy1line = "Hier geht es zum Kunden:"."\n";
|
||||
$button = "zum Kunden";
|
||||
$title = "Ein Kunden muss überprüft werden und einem Berater zugeordnet werden, da die Adresse nicht eindeutig ist.";
|
||||
$url = route('admin_customer_detail', $this->user->id);
|
||||
$url = config('app.url_crm') . '/admin/customer/detail/' . $this->user->id;
|
||||
$content .= $this->user ? 'Firma: '.$this->user->billing_company."\n" : '';
|
||||
$content .= \App\Services\HTMLHelper::getSalutationLang($this->user->billing_salutation)." ";
|
||||
$content .= $this->user->billing_firstname." ";
|
||||
|
|
@ -86,7 +86,7 @@ class MailInfo extends Mailable
|
|||
$copy1line = "Hier geht es zum Kunden:"."\n";
|
||||
$button = "zum Kunden";
|
||||
$title = "Ein Kunden muss erneut überprüft werden, da bei einer Änderung eine bestehende Kundenhoheit gefunden wurde.";
|
||||
$url = route('admin_customer_detail', $this->user->id);
|
||||
$url = config('app.url_crm') . '/admin/customer/detail/' . $this->user->id;
|
||||
$content .= "Folgende Daten für die Kundenhoheit wurden geändert:"."\n";
|
||||
foreach ($this->data as $key=>$value){
|
||||
$content .= $this->user->{$key}." => ".$value."\n";
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class MailReleaseAccount extends Mailable
|
|||
$copy1line .= 'Der Berater erhält eine Mail, dass sein Account freigeschaltet wurde. Der Vertrag wird automatisch mit den Daten des Vertriebspartners erstellt.'."\n";
|
||||
|
||||
return $this->view('emails.info')->with([
|
||||
'url' => route('admin_lead_edit', $this->user->id).'?show=check_lead',
|
||||
'url' => config('app.url_crm') . '/admin/lead/edit/' . $this->user->id . '?show=check_lead',
|
||||
'title' => 'Berater Registrierung prüfen',
|
||||
'button' => 'zur Berater Prüfung',
|
||||
'copy1line' => $copy1line,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class MailReleaseDocument extends Mailable
|
|||
$copy1line .= 'Bei fehlerhafter Angabe nimm bitte kontakt mit dem Berater auf.'."\n";
|
||||
|
||||
return $this->view('emails.info')->with([
|
||||
'url' => route('admin_lead_edit', $this->user->id).'?show=check_lead',
|
||||
'url' => config('app.url_crm') . '/admin/lead/edit/' . $this->user->id . '?show=check_lead',
|
||||
'title' => 'Berater Unterlagen prüfen',
|
||||
'button' => 'zur Berater Prüfung',
|
||||
'copy1line' => $copy1line,
|
||||
|
|
|
|||
407
app/Models/DhlShipment.php
Normal file
407
app/Models/DhlShipment.php
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* DHL Shipment Model
|
||||
*
|
||||
* Represents a DHL shipment for a shopping order, including both outbound and return shipments.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $shopping_order_id
|
||||
* @property string|null $shipment_number DHL shipment number
|
||||
* @property string|null $tracking_number DHL tracking number
|
||||
* @property string $type Type: 'outbound' or 'return'
|
||||
* @property int|null $related_shipment_id For returns: reference to original shipment
|
||||
* @property float $weight Package weight in kg
|
||||
* @property int|null $length Package length in cm
|
||||
* @property int|null $width Package width in cm
|
||||
* @property int|null $height Package height in cm
|
||||
* @property string $product_code DHL product code (e.g., V01PAK)
|
||||
* @property array|null $services Additional DHL services
|
||||
* @property string|null $label_path Path to generated label file
|
||||
* @property string $label_format Label format (PDF or ZPL)
|
||||
* @property bool $label_printed Whether label has been printed
|
||||
* @property string $status Shipment status
|
||||
* @property string|null $tracking_status Current tracking status from DHL
|
||||
* @property string|null $tracking_details Detailed tracking information (JSON)
|
||||
* @property Carbon|null $last_tracked_at Last tracking update
|
||||
* @property string $recipient_name Recipient name
|
||||
* @property string|null $recipient_company Recipient company
|
||||
* @property string $recipient_street Recipient street
|
||||
* @property string $recipient_street_number Recipient street number
|
||||
* @property string $recipient_postal_code Recipient postal code
|
||||
* @property string $recipient_city Recipient city
|
||||
* @property string|null $recipient_state Recipient state
|
||||
* @property string $recipient_country Recipient country code
|
||||
* @property string|null $recipient_email Recipient email
|
||||
* @property string|null $recipient_phone Recipient phone
|
||||
* @property array|null $api_request_data API request data for debugging
|
||||
* @property array|null $api_response_data API response data for debugging
|
||||
* @property string|null $api_errors API error messages
|
||||
* @property float|null $shipping_cost Shipping cost
|
||||
* @property string $currency Currency code
|
||||
* @property string|null $notes Internal notes
|
||||
* @property array|null $metadata Additional metadata
|
||||
* @property Carbon|null $shipped_at When the package was shipped
|
||||
* @property Carbon|null $delivered_at When the package was delivered
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property-read ShoppingOrder $shoppingOrder
|
||||
* @property-read DhlShipment|null $relatedShipment
|
||||
* @property-read DhlShipment|null $returnShipment
|
||||
* @property-read string|null $dimensions
|
||||
* @property-read string|null $label_url
|
||||
* @property-read string $recipient_address
|
||||
* @property-read string $status_label
|
||||
* @property-read string $type_label
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment active()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment outbound()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment returns()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment trackable()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereApiErrors($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereApiRequestData($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereApiResponseData($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereCurrency($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereDeliveredAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereHeight($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLabelFormat($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLabelPath($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLabelPrinted($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLastTrackedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereLength($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereMetadata($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereNotes($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereProductCode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientCity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientCompany($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientCountry($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientPhone($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientPostalCode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientState($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientStreet($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRecipientStreetNumber($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereRelatedShipmentId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereServices($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShipmentNumber($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShippedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShippingCost($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereShoppingOrderId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereTrackingDetails($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereTrackingNumber($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereTrackingStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereWeight($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DhlShipment whereWidth($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class DhlShipment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*/
|
||||
protected $table = 'dhl_shipments';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*/
|
||||
protected $fillable = [
|
||||
'shopping_order_id',
|
||||
'shipment_number',
|
||||
'tracking_number',
|
||||
'type',
|
||||
'related_shipment_id',
|
||||
'weight',
|
||||
'length',
|
||||
'width',
|
||||
'height',
|
||||
'product_code',
|
||||
'services',
|
||||
'label_path',
|
||||
'label_format',
|
||||
'label_printed',
|
||||
'status',
|
||||
'tracking_status',
|
||||
'tracking_details',
|
||||
'last_tracked_at',
|
||||
'recipient_name',
|
||||
'recipient_company',
|
||||
'recipient_street',
|
||||
'recipient_street_number',
|
||||
'recipient_postal_code',
|
||||
'recipient_city',
|
||||
'recipient_state',
|
||||
'recipient_country',
|
||||
'recipient_email',
|
||||
'recipient_phone',
|
||||
'api_request_data',
|
||||
'api_response_data',
|
||||
'api_errors',
|
||||
'shipping_cost',
|
||||
'currency',
|
||||
'notes',
|
||||
'metadata',
|
||||
'shipped_at',
|
||||
'delivered_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*/
|
||||
protected $casts = [
|
||||
'weight' => 'float',
|
||||
'length' => 'integer',
|
||||
'width' => 'integer',
|
||||
'height' => 'integer',
|
||||
'services' => 'array',
|
||||
'label_printed' => 'boolean',
|
||||
'tracking_details' => 'array',
|
||||
'last_tracked_at' => 'datetime',
|
||||
'api_request_data' => 'array',
|
||||
'api_response_data' => 'array',
|
||||
'shipping_cost' => 'decimal:2',
|
||||
'metadata' => 'array',
|
||||
'shipped_at' => 'datetime',
|
||||
'delivered_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Shipment types
|
||||
*/
|
||||
public const TYPE_OUTBOUND = 'outbound';
|
||||
public const TYPE_RETURN = 'return';
|
||||
|
||||
/**
|
||||
* Shipment statuses
|
||||
*/
|
||||
public const STATUS_CREATED = 'created';
|
||||
public const STATUS_SUBMITTED = 'submitted';
|
||||
public const STATUS_IN_TRANSIT = 'in_transit';
|
||||
public const STATUS_DELIVERED = 'delivered';
|
||||
public const STATUS_RETURNED = 'returned';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
public const STATUS_FAILED = 'failed';
|
||||
|
||||
/**
|
||||
* Get the shopping order that owns the shipment
|
||||
*/
|
||||
public function shoppingOrder(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ShoppingOrder::class, 'shopping_order_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the related shipment (for returns)
|
||||
*/
|
||||
public function relatedShipment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(DhlShipment::class, 'related_shipment_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the return shipment (for outbound shipments)
|
||||
*/
|
||||
public function returnShipment(): HasOne
|
||||
{
|
||||
return $this->hasOne(DhlShipment::class, 'related_shipment_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for outbound shipments
|
||||
*/
|
||||
public function scopeOutbound($query)
|
||||
{
|
||||
return $query->where('type', self::TYPE_OUTBOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for return shipments
|
||||
*/
|
||||
public function scopeReturns($query)
|
||||
{
|
||||
return $query->where('type', self::TYPE_RETURN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for active shipments (not cancelled or failed)
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->whereNotIn('status', [self::STATUS_CANCELLED, self::STATUS_FAILED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for trackable shipments (have tracking number)
|
||||
*/
|
||||
public function scopeTrackable($query)
|
||||
{
|
||||
return $query->whereNotNull('tracking_number');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if shipment is outbound
|
||||
*/
|
||||
public function isOutbound(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_OUTBOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if shipment is return
|
||||
*/
|
||||
public function isReturn(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_RETURN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if shipment can be cancelled
|
||||
*/
|
||||
public function canBeCancelled(): bool
|
||||
{
|
||||
return in_array($this->status, [
|
||||
self::STATUS_CREATED,
|
||||
self::STATUS_SUBMITTED,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if shipment is delivered
|
||||
*/
|
||||
public function isDelivered(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_DELIVERED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if shipment has tracking information
|
||||
*/
|
||||
public function hasTracking(): bool
|
||||
{
|
||||
return !empty($this->tracking_number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if label is available
|
||||
*/
|
||||
public function hasLabel(): bool
|
||||
{
|
||||
return !empty($this->label_path) && file_exists(storage_path('app/' . $this->label_path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full recipient address as formatted string
|
||||
*/
|
||||
public function getRecipientAddressAttribute(): string
|
||||
{
|
||||
$address = $this->recipient_name;
|
||||
|
||||
if ($this->recipient_company) {
|
||||
$address .= "\n" . $this->recipient_company;
|
||||
}
|
||||
|
||||
$address .= "\n" . $this->recipient_street . ' ' . $this->recipient_street_number;
|
||||
$address .= "\n" . $this->recipient_postal_code . ' ' . $this->recipient_city;
|
||||
|
||||
if ($this->recipient_state) {
|
||||
$address .= "\n" . $this->recipient_state;
|
||||
}
|
||||
|
||||
$address .= "\n" . $this->recipient_country;
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package dimensions as formatted string
|
||||
*/
|
||||
public function getDimensionsAttribute(): ?string
|
||||
{
|
||||
if (!$this->length || !$this->width || !$this->height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->length . ' x ' . $this->width . ' x ' . $this->height . ' cm';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable status
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return match($this->status) {
|
||||
self::STATUS_CREATED => 'Erstellt',
|
||||
self::STATUS_SUBMITTED => 'Übertragen',
|
||||
self::STATUS_IN_TRANSIT => 'Unterwegs',
|
||||
self::STATUS_DELIVERED => 'Zugestellt',
|
||||
self::STATUS_RETURNED => 'Zurückgeschickt',
|
||||
self::STATUS_CANCELLED => 'Storniert',
|
||||
self::STATUS_FAILED => 'Fehler',
|
||||
default => 'Unbekannt',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-readable type
|
||||
*/
|
||||
public function getTypeLabelAttribute(): string
|
||||
{
|
||||
return match($this->type) {
|
||||
self::TYPE_OUTBOUND => 'Versand',
|
||||
self::TYPE_RETURN => 'Retoure',
|
||||
default => 'Unbekannt',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get label file URL for download
|
||||
*/
|
||||
public function getLabelUrlAttribute(): ?string
|
||||
{
|
||||
if (!$this->hasLabel()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return route('admin.dhl.shipments.label', $this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the model
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($shipment) {
|
||||
// Set default values
|
||||
if (empty($shipment->currency)) {
|
||||
$shipment->currency = config('dhl.defaults.currency', 'EUR');
|
||||
}
|
||||
|
||||
if (empty($shipment->product_code)) {
|
||||
$shipment->product_code = config('dhl.defaults.product', 'V01PAK');
|
||||
}
|
||||
|
||||
if (empty($shipment->label_format)) {
|
||||
$shipment->label_format = config('dhl.labels.format', 'PDF');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +101,12 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
* @property int|null $abo_interval
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereAboInterval($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ShoppingOrder whereIsAbo($value)
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\DhlShipment> $dhlOutboundShipments
|
||||
* @property-read int|null $dhl_outbound_shipments_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\DhlShipment> $dhlReturnShipments
|
||||
* @property-read int|null $dhl_return_shipments_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\DhlShipment> $dhlShipments
|
||||
* @property-read int|null $dhl_shipments_count
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ShoppingOrder extends Model
|
||||
|
|
@ -623,4 +629,44 @@ class ShoppingOrder extends Model
|
|||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DHL shipments for this order
|
||||
*/
|
||||
public function dhlShipments()
|
||||
{
|
||||
return $this->hasMany('App\Models\DhlShipment', 'shopping_order_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get outbound DHL shipments only
|
||||
*/
|
||||
public function dhlOutboundShipments()
|
||||
{
|
||||
return $this->hasMany('App\Models\DhlShipment', 'shopping_order_id')->where('type', 'outbound');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get return DHL shipments only
|
||||
*/
|
||||
public function dhlReturnShipments()
|
||||
{
|
||||
return $this->hasMany('App\Models\DhlShipment', 'shopping_order_id')->where('type', 'return');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if order has DHL shipments
|
||||
*/
|
||||
public function hasDhlShipments(): bool
|
||||
{
|
||||
return $this->dhlShipments()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest DHL shipment
|
||||
*/
|
||||
public function getLatestDhlShipment()
|
||||
{
|
||||
return $this->dhlShipments()->latest()->first();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
7
app/Providers/AppServiceProvider.php
Executable file → Normal file
7
app/Providers/AppServiceProvider.php
Executable file → Normal file
|
|
@ -16,7 +16,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function boot()
|
||||
{
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
|
||||
if ($this->app->environment('production')) {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
\View::composer('*', function ($view) {
|
||||
try {
|
||||
$context = app(\App\Domain\DomainContext::class);
|
||||
|
||||
|
||||
// Für die Main-Domain: user_shop immer auf null setzen
|
||||
if ($context->type === 'main') {
|
||||
$view->with('user_shop', null);
|
||||
|
|
@ -49,9 +49,8 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function register()
|
||||
{
|
||||
|
||||
if ($this->app->environment() !== 'production') {
|
||||
if ($this->app->environment() !== 'production' && class_exists(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class)) {
|
||||
$this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
|
||||
}
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
app/Providers/AuthServiceProvider.php
Executable file → Normal file
0
app/Providers/AuthServiceProvider.php
Executable file → Normal file
0
app/Providers/BroadcastServiceProvider.php
Executable file → Normal file
0
app/Providers/BroadcastServiceProvider.php
Executable file → Normal file
|
|
@ -44,10 +44,12 @@ class DomainServiceProvider extends ServiceProvider
|
|||
|
||||
// Analysiere den Host der aktuellen Anfrage
|
||||
$domainInfo = $domainService->parseDomain($request->getHost());
|
||||
\Log::debug('DomainServiceProvider: domainInfo', [
|
||||
'domainInfo' => $domainInfo,
|
||||
'host' => $request->getHost()
|
||||
]);
|
||||
if (config('app.debug')) {
|
||||
\Log::debug('DomainServiceProvider: domainInfo', [
|
||||
'domainInfo' => $domainInfo,
|
||||
'host' => $request->getHost()
|
||||
]);
|
||||
}
|
||||
$userShop = null;
|
||||
|
||||
// Wenn es sich um eine User-Shop-Domain handelt, versuche das Shop-Objekt zu finden.
|
||||
|
|
|
|||
0
app/Providers/EventServiceProvider.php
Executable file → Normal file
0
app/Providers/EventServiceProvider.php
Executable file → Normal file
0
app/Providers/RouteServiceProvider.php
Executable file → Normal file
0
app/Providers/RouteServiceProvider.php
Executable file → Normal file
0
app/Providers/YardServiceProvider.php
Executable file → Normal file
0
app/Providers/YardServiceProvider.php
Executable file → Normal file
1312
app/Services/DhlApiService.php
Normal file
1312
app/Services/DhlApiService.php
Normal file
File diff suppressed because it is too large
Load diff
89
app/Services/DhlDataHelper.php
Normal file
89
app/Services/DhlDataHelper.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Http\Controllers\SettingController;
|
||||
|
||||
/**
|
||||
* DHL Data Helper
|
||||
*
|
||||
* Central class for preparing DHL API data structures
|
||||
* Prevents code duplication between DhlShipmentService and CreateShipmentJob
|
||||
*/
|
||||
class DhlDataHelper
|
||||
{
|
||||
/**
|
||||
* Prepare order data for DHL API v2
|
||||
*
|
||||
* Structure matches official DHL API v2 createOrders endpoint:
|
||||
* https://developer.dhl.com/api-reference/parcel-de-shipping-post-parcel-germany-v2
|
||||
*
|
||||
* @param ShoppingOrder $order
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
* @param array|null $dhlConfig Optional pre-loaded config (for queue jobs)
|
||||
* @return array
|
||||
*/
|
||||
public static function prepareOrderData(ShoppingOrder $order, float $weight, array $options = [], ?array $dhlConfig = null): array
|
||||
{
|
||||
\Log::info('prepareOrderData', $options);
|
||||
//die daten für das versandlabel werden immer aus dem Formular genommen, damit anpassungen möglich sind
|
||||
if (!isset($options['shipping_address'])) {
|
||||
throw new \Exception('shipping_address is required');
|
||||
}
|
||||
$shippingAddress = $options['shipping_address'];
|
||||
|
||||
// Get DHL configuration for shipper data
|
||||
if ($dhlConfig === null) {
|
||||
$settingController = new SettingController();
|
||||
$dhlConfig = $settingController->getDhlConfig();
|
||||
}
|
||||
return [
|
||||
'order_id' => $order->id,
|
||||
'weight_kg' => $weight,
|
||||
'product_code' => $options['product_code'] ?? 'V01PAK',
|
||||
'label_format' => $options['label_format'] ?? $dhlConfig['label_format'] ?? 'PDF',
|
||||
'print_format' => $options['print_format'] ?? $dhlConfig['print_format'] ?? null,
|
||||
'retoure_print_format' => $options['retoure_print_format'] ?? $dhlConfig['retoure_print_format'] ?? null,
|
||||
|
||||
// Shipper data (sender) - from admin settings
|
||||
'shipper' => [
|
||||
'name' => $dhlConfig['sender']['company'] ?? 'mivita care gmbh',
|
||||
'name2' => $dhlConfig['sender']['name'] ?? '',
|
||||
'street' => $dhlConfig['sender']['street'] ?? 'Leinfeld',
|
||||
'houseNumber' => $dhlConfig['sender']['house_number'] ?? '2',
|
||||
'postalCode' => $dhlConfig['sender']['postalCode'] ?? '87755',
|
||||
'city' => $dhlConfig['sender']['city'] ?? 'Kirchhaslach',
|
||||
'country' => $dhlConfig['sender']['country'] ?? 'DE',
|
||||
'email' => $dhlConfig['sender']['email'] ?? 'versand@mivita.care',
|
||||
'phone' => $dhlConfig['sender']['phone'] ?? '+49 123 456789',
|
||||
],
|
||||
|
||||
// Consignee data (recipient) - from order
|
||||
'consignee' => [
|
||||
'name' => $shippingAddress['firstname'] ?? '' . ' ' . $shippingAddress['lastname'] ?? '',
|
||||
'name2' => $shippingAddress['company'] ?? '',
|
||||
'street' => $shippingAddress['address'] ?? '',
|
||||
'houseNumber' => $shippingAddress['houseNumber'] ?? '',
|
||||
'postalCode' => $shippingAddress['zipcode'] ?? '',
|
||||
'city' => $shippingAddress['city'] ?? '',
|
||||
'country' => $shippingAddress['country']?->code ?? 'DE',
|
||||
'email' => $shippingAddress['email'] ?? '',
|
||||
'phone' => $shippingAddress['phone'] ?? '',
|
||||
],
|
||||
// Package dimensions from options or defaults
|
||||
'dimensions' => [
|
||||
'length' => $options['length'] ?? 30,
|
||||
'width' => $options['width'] ?? 25,
|
||||
'height' => $options['height'] ?? 10,
|
||||
],
|
||||
|
||||
// Additional services
|
||||
'services' => $options['services'] ?? [],
|
||||
|
||||
// Custom reference for tracking
|
||||
'reference' => 'Order-' . $order->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
439
app/Services/DhlModalService.php
Normal file
439
app/Services/DhlModalService.php
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
<?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'] ?? '')
|
||||
];
|
||||
}
|
||||
}
|
||||
147
app/Services/DhlShipmentService.php
Normal file
147
app/Services/DhlShipmentService.php
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Http\Controllers\SettingController;
|
||||
use App\Jobs\CreateShipmentJob;
|
||||
use App\Services\DhlDataHelper;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* DHL Shipment Service
|
||||
*
|
||||
* Handles both synchronous and asynchronous shipment creation
|
||||
* based on configuration settings
|
||||
*/
|
||||
class DhlShipmentService
|
||||
{
|
||||
/**
|
||||
* Create a DHL shipment (sync or async based on config)
|
||||
*
|
||||
* @param ShoppingOrder $order
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function createShipment(ShoppingOrder $order, float $weight = 1.0, array $options = []): array
|
||||
{
|
||||
// Get DHL configuration
|
||||
$settingController = new SettingController();
|
||||
$dhlConfig = $settingController->getDhlConfig();
|
||||
\Log::info('dhlConfig', $dhlConfig);
|
||||
// Check if queue should be used
|
||||
$useQueue = $dhlConfig['use_queue'] ?? false;
|
||||
|
||||
if ($useQueue) {
|
||||
return $this->createShipmentAsync($order, $weight, $options, $dhlConfig);
|
||||
} else {
|
||||
return $this->createShipmentSync($order, $weight, $options, $dhlConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create shipment asynchronously using queue
|
||||
*
|
||||
* @param ShoppingOrder $order
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
* @param array $dhlConfig
|
||||
* @return array
|
||||
*/
|
||||
private function createShipmentAsync(ShoppingOrder $order, float $weight, array $options, array $dhlConfig): array
|
||||
{
|
||||
try {
|
||||
// Dispatch job with pre-loaded config
|
||||
CreateShipmentJob::dispatch($order, $weight, $options, $dhlConfig);
|
||||
|
||||
Log::info('[DHL Service] Shipment creation dispatched to queue', [
|
||||
'order_id' => $order->id,
|
||||
'weight' => $weight
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Sendung wird erstellt. Sie erhalten eine Benachrichtigung, sobald das Versandlabel verfügbar ist.',
|
||||
'queued' => true,
|
||||
'order_id' => $order->id
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
Log::error('[DHL Service] Failed to dispatch shipment creation', [
|
||||
'error' => $e->getMessage(),
|
||||
'order_id' => $order->id,
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Fehler beim Einreihen der Sendungserstellung: ' . $e->getMessage(),
|
||||
'queued' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create shipment synchronously
|
||||
*
|
||||
* @param ShoppingOrder $order
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
* @param array $dhlConfig
|
||||
* @return array
|
||||
*/
|
||||
private function createShipmentSync(ShoppingOrder $order, float $weight, array $options, array $dhlConfig): array
|
||||
{
|
||||
try {
|
||||
Log::info('[DHL Service] Creating shipment synchronously', [
|
||||
'order_id' => $order->id,
|
||||
'weight' => $weight
|
||||
]);
|
||||
|
||||
// Create DHL client directly
|
||||
$dhlClient = new \Acme\Dhl\Support\DhlClient(
|
||||
$dhlConfig['base_url'],
|
||||
$dhlConfig['api_key'],
|
||||
$dhlConfig['username'],
|
||||
$dhlConfig['password']
|
||||
);
|
||||
|
||||
$shippingService = new \Acme\Dhl\Services\ShippingService($dhlClient);
|
||||
|
||||
// Prepare order data using helper
|
||||
$orderData = DhlDataHelper::prepareOrderData($order, $weight, $options, $dhlConfig);
|
||||
Log::info('orderData', $orderData);
|
||||
// Create the shipment directly
|
||||
$result = $shippingService->createLabel($orderData);
|
||||
|
||||
Log::info('[DHL Service] Shipment created successfully (sync)', [
|
||||
'order_id' => $order->id,
|
||||
'shipment_number' => $result['shipmentNumber'] ?? 'N/A',
|
||||
'label_path' => $result['labelPath'] ?? 'N/A',
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Versandlabel erfolgreich erstellt!',
|
||||
'queued' => false,
|
||||
'order_id' => $order->id,
|
||||
'shipment_number' => $result['shipmentNumber'] ?? null,
|
||||
'tracking_number' => $result['trackingNumber'] ?? null,
|
||||
'label_path' => $result['labelPath'] ?? null,
|
||||
'label_url' => $result['labelUrl'] ?? null,
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
Log::error('[DHL Service] Shipment creation failed (sync)', [
|
||||
'order_id' => $order->id,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Fehler beim Erstellen des Versandlabels: ' . $e->getMessage(),
|
||||
'queued' => false,
|
||||
'order_id' => $order->id
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +126,9 @@ class DomainService
|
|||
$subdomain = null;
|
||||
if (count($parts) > 2) {
|
||||
$subdomain = $parts[0];
|
||||
\Log::debug('DomainService: Using extracted subdomain', ['subdomain' => $subdomain, 'host' => $host]);
|
||||
if (config('app.debug')) {
|
||||
\Log::debug('DomainService: Using extracted subdomain', ['subdomain' => $subdomain, 'host' => $host]);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine domain type based on subdomain and host
|
||||
|
|
|
|||
|
|
@ -270,6 +270,9 @@ class Payment
|
|||
public static function paymentStatusSendMail(ShoppingOrder $shopping_order, $shopping_payment, $data){
|
||||
$bcc = [];
|
||||
$billing_email = $shopping_order->shopping_user->billing_email;
|
||||
|
||||
// Überprüfung der Billing-E-Mail-Adresse
|
||||
|
||||
if(!$billing_email){
|
||||
if($data['mode'] === 'test'){
|
||||
$billing_email = config('app.checkout_test_mail');
|
||||
|
|
@ -277,6 +280,11 @@ class Payment
|
|||
$billing_email = config('app.checkout_mail');
|
||||
}
|
||||
}
|
||||
if(!filter_var($billing_email, FILTER_VALIDATE_EMAIL)){
|
||||
\Log::channel('payment')->error("Invalid billing email at shopping_order ".$shopping_order->id, ['billing_email' => $billing_email]);
|
||||
$billing_email = config('app.checkout_mail');
|
||||
}
|
||||
|
||||
if($data['mode'] === 'test'){
|
||||
$bcc[] = config('app.checkout_test_mail');
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Country;
|
||||
|
|
@ -13,7 +14,7 @@ class Util
|
|||
{
|
||||
|
||||
private static $postRoute = 'base.';
|
||||
|
||||
|
||||
public static function getToken()
|
||||
{
|
||||
return hash_hmac('sha256', Str::random(40), config('app.key'));
|
||||
|
|
@ -23,87 +24,95 @@ class Util
|
|||
{
|
||||
$uuid = (string) Str::uuid();
|
||||
$e_uuid = explode("-", $uuid);
|
||||
if(isset($e_uuid[0]) && $e_uuid[1]){
|
||||
return $e_uuid[0]."-".$e_uuid[1];
|
||||
|
||||
if (isset($e_uuid[0]) && $e_uuid[1]) {
|
||||
return $e_uuid[0] . "-" . $e_uuid[1];
|
||||
}
|
||||
return $uuid;
|
||||
}
|
||||
|
||||
public static function formatDate(){
|
||||
if(\App::getLocale() === "en"){
|
||||
public static function formatDate()
|
||||
{
|
||||
if (\App::getLocale() === "en") {
|
||||
return 'yyyy-mm-dd';
|
||||
}
|
||||
return 'dd.mm.yyyy';
|
||||
|
||||
}
|
||||
|
||||
public static function formatDateDB(){
|
||||
if(\App::getLocale() === "en"){
|
||||
public static function formatDateDB()
|
||||
{
|
||||
if (\App::getLocale() === "en") {
|
||||
return 'Y-m-d';
|
||||
}
|
||||
return 'd.m.Y';
|
||||
}
|
||||
|
||||
public static function formatDateTimeDB(){
|
||||
if(\App::getLocale() === "en"){
|
||||
public static function formatDateTimeDB()
|
||||
{
|
||||
if (\App::getLocale() === "en") {
|
||||
return 'Y-m-d - H:i';
|
||||
}
|
||||
return 'd.m.Y - H:i';
|
||||
}
|
||||
|
||||
public static function _format_number($value){
|
||||
public static function _format_number($value)
|
||||
{
|
||||
return preg_replace("/[^0-9,-]/", "", $value);
|
||||
|
||||
}
|
||||
|
||||
public static function _thousands_separator(){
|
||||
return \App::getLocale() === "en" ? ',' : '.';
|
||||
public static function _thousands_separator()
|
||||
{
|
||||
return \App::getLocale() === "en" ? ',' : '.';
|
||||
}
|
||||
|
||||
public static function _decimal_separator(){
|
||||
return \App::getLocale() === "en" ? '.' : ',';
|
||||
public static function _decimal_separator()
|
||||
{
|
||||
return \App::getLocale() === "en" ? '.' : ',';
|
||||
}
|
||||
|
||||
|
||||
public static function maxStrLength($str, $length = 40){
|
||||
public static function maxStrLength($str, $length = 40)
|
||||
{
|
||||
|
||||
if(strlen($str) > $length){
|
||||
if (strlen($str) > $length) {
|
||||
$str = substr($str, 0, $length);
|
||||
//$str = substr($str, 0, strrpos($str, " "));
|
||||
$str = $str." ...";
|
||||
$str = $str . " ...";
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
public static function reFormatNumber($value){
|
||||
|
||||
public static function reFormatNumber($value)
|
||||
{
|
||||
return (float) str_replace(',', '.', self::_format_number($value));
|
||||
}
|
||||
|
||||
public static function formatNumber($value, $dec=2){
|
||||
public static function formatNumber($value, $dec = 2)
|
||||
{
|
||||
$value = floatval(str_replace(',', '', $value));
|
||||
return number_format($value, $dec, self::_decimal_separator(), self::_thousands_separator());
|
||||
|
||||
}
|
||||
public static function cleanIntegerFromString($value) {
|
||||
public static function cleanIntegerFromString($value)
|
||||
{
|
||||
// Entferne alle nicht-numerischen Zeichen außer Minus
|
||||
$cleanStr = preg_replace("/[^0-9-]/", "", $value);
|
||||
|
||||
|
||||
// Konvertiere zu Integer und entferne führende Nullen
|
||||
$number = (int)$cleanStr;
|
||||
|
||||
|
||||
return $number;
|
||||
}
|
||||
public static function cleanNumberFormat($num = 0, $dec = 2, $fullzero = false){
|
||||
|
||||
if($fullzero && $num == 0){
|
||||
public static function cleanNumberFormat($num = 0, $dec = 2, $fullzero = false)
|
||||
{
|
||||
|
||||
if ($fullzero && $num == 0) {
|
||||
return number_format($num, $dec, self::_decimal_separator(), self::_thousands_separator());
|
||||
}
|
||||
return rtrim(rtrim(number_format($num, $dec, self::_decimal_separator(), self::_thousands_separator()),'0'), self::_decimal_separator());
|
||||
return rtrim(rtrim(number_format($num, $dec, self::_decimal_separator(), self::_thousands_separator()), '0'), self::_decimal_separator());
|
||||
}
|
||||
|
||||
public static function utf8ize( $mixed ) {
|
||||
public static function utf8ize($mixed)
|
||||
{
|
||||
if (is_array($mixed)) {
|
||||
foreach ($mixed as $key => $value) {
|
||||
$mixed[$key] = self::utf8ize($value);
|
||||
|
|
@ -115,14 +124,17 @@ class Util
|
|||
}
|
||||
|
||||
|
||||
public static function getPostRoute(){
|
||||
public static function getPostRoute()
|
||||
{
|
||||
return self::$postRoute;
|
||||
}
|
||||
public static function setPostRoute($postRoute){
|
||||
public static function setPostRoute($postRoute)
|
||||
{
|
||||
self::$postRoute = $postRoute;
|
||||
}
|
||||
|
||||
public static function getUserShop(){
|
||||
public static function getUserShop()
|
||||
{
|
||||
$shop = session('user_shop');
|
||||
if (empty($shop) || !is_object($shop)) {
|
||||
return null;
|
||||
|
|
@ -130,17 +142,19 @@ class Util
|
|||
return $shop;
|
||||
}
|
||||
|
||||
public static function getDefaultUserShop(){
|
||||
public static function getDefaultUserShop()
|
||||
{
|
||||
$user = \App\User::find(6);
|
||||
if($user && $user->shop){
|
||||
if ($user && $user->shop) {
|
||||
return $user->shop;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getAuthUser(){
|
||||
if(\Session::has('auth_user')){
|
||||
if($auth_user = \Session::get('auth_user')){
|
||||
public static function getAuthUser()
|
||||
{
|
||||
if (\Session::has('auth_user')) {
|
||||
if ($auth_user = \Session::get('auth_user')) {
|
||||
return $auth_user;
|
||||
}
|
||||
}
|
||||
|
|
@ -148,91 +162,102 @@ class Util
|
|||
}
|
||||
|
||||
|
||||
public static function getUserShopIdentifier(){
|
||||
if(\Session::has('user_shop_identifier')){
|
||||
if($user_shop_identifier = \Session::get('user_shop_identifier')){
|
||||
public static function getUserShopIdentifier()
|
||||
{
|
||||
if (\Session::has('user_shop_identifier')) {
|
||||
if ($user_shop_identifier = \Session::get('user_shop_identifier')) {
|
||||
return $user_shop_identifier;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getInstanceStatus(){
|
||||
public static function getInstanceStatus()
|
||||
{
|
||||
$identifier = self::getUserShopIdentifier();
|
||||
if($identifier && \Session::has('user_shop_payment') && \Session::get('user_shop_payment') === 6){
|
||||
if ($identifier && \Session::has('user_shop_payment') && \Session::get('user_shop_payment') === 6) {
|
||||
return OrderPaymentService::getInstanceStatus($identifier);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function setInstanceStatus($status, $lower = true){
|
||||
public static function setInstanceStatus($status, $lower = true)
|
||||
{
|
||||
$identifier = self::getUserShopIdentifier();
|
||||
if($identifier && \Session::has('user_shop_payment') && \Session::get('user_shop_payment') === 6){
|
||||
if ($identifier && \Session::has('user_shop_payment') && \Session::get('user_shop_payment') === 6) {
|
||||
OrderPaymentService::updateInstanceStatus($identifier, $status, $lower);
|
||||
}
|
||||
}
|
||||
|
||||
public static function setInstanceStatusByPayment($shopping_payment, $status, $lower = true){
|
||||
if($shopping_payment->identifier){
|
||||
public static function setInstanceStatusByPayment($shopping_payment, $status, $lower = true)
|
||||
{
|
||||
if ($shopping_payment->identifier) {
|
||||
OrderPaymentService::updateInstanceStatus($shopping_payment->identifier, $status, $lower);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getShoppingInstance(){
|
||||
if(\Session::has('shopping_instance')){
|
||||
public static function getShoppingInstance()
|
||||
{
|
||||
if (\Session::has('shopping_instance')) {
|
||||
return \Session::get('shopping_instance');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getUserHistory(){
|
||||
public static function getUserHistory()
|
||||
{
|
||||
$auth_user = self::getAuthUser();
|
||||
$user_shop_identifier = self::getUserShopIdentifier();
|
||||
if($user_shop_identifier && $auth_user){
|
||||
if ($user_shop_identifier && $auth_user) {
|
||||
return UserHistory::whereUserId($auth_user->id)->whereIdentifier($user_shop_identifier)->get()->last();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function setUserHistoryValue($values = []){
|
||||
if($user_history = self::getUserHistory()){
|
||||
foreach ($values as $key=>$val){
|
||||
public static function setUserHistoryValue($values = [])
|
||||
{
|
||||
if ($user_history = self::getUserHistory()) {
|
||||
foreach ($values as $key => $val) {
|
||||
$user_history->{$key} = $val;
|
||||
}
|
||||
$user_history->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getUserHistoryValue($key){
|
||||
if($user_history = self::getUserHistory()) {
|
||||
public static function getUserHistoryValue($key)
|
||||
{
|
||||
if ($user_history = self::getUserHistory()) {
|
||||
return $user_history->{$key};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getUserShoppingMode(){
|
||||
if($auth_user = self::getAuthUser()){
|
||||
if($auth_user->isTestMode()){
|
||||
public static function getUserShoppingMode()
|
||||
{
|
||||
if ($auth_user = self::getAuthUser()) {
|
||||
if ($auth_user->isTestMode()) {
|
||||
return 'test';
|
||||
}
|
||||
}
|
||||
return config('app.mode');
|
||||
}
|
||||
public static function addRoute($p = []){
|
||||
public static function addRoute($p = [])
|
||||
{
|
||||
$b = [];
|
||||
if(\Session::has('user_shop')){
|
||||
if($user_shop = \Session::get('user_shop')){
|
||||
if (\Session::has('user_shop')) {
|
||||
if ($user_shop = \Session::get('user_shop')) {
|
||||
$b = ['subdomain' => $user_shop->slug];
|
||||
}
|
||||
}
|
||||
return array_merge($p, $b);
|
||||
}
|
||||
|
||||
public static function checkUserLandIsNot($user){
|
||||
public static function checkUserLandIsNot($user)
|
||||
{
|
||||
|
||||
if(isset($user->account->country_id)){
|
||||
if (isset($user->account->country_id)) {
|
||||
//ch schweiz is out
|
||||
if($user->account->country_id === 6){
|
||||
if ($user->account->country_id === 6) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -241,9 +266,10 @@ class Util
|
|||
}
|
||||
|
||||
|
||||
public static function getMyMivitaShopUrl($add_url = ""){
|
||||
if(\Session::has('user_shop_domain')){
|
||||
$url = \Session::get('user_shop_domain').$add_url;
|
||||
public static function getMyMivitaShopUrl($add_url = "")
|
||||
{
|
||||
if (\Session::has('user_shop_domain')) {
|
||||
$url = \Session::get('user_shop_domain') . $add_url;
|
||||
if (!str_starts_with($url, 'http')) {
|
||||
$url = 'https://' . ltrim($url, '/');
|
||||
}
|
||||
|
|
@ -251,83 +277,90 @@ class Util
|
|||
}
|
||||
//alois sein shop
|
||||
$user = \App\User::find(6);
|
||||
if($user && $user->shop){
|
||||
return config('app.protocol').$user->shop->slug.".".config('app.domain').config('app.tld_care').$add_url;
|
||||
if ($user && $user->shop) {
|
||||
return config('app.protocol') . $user->shop->slug . "." . config('app.domain') . config('app.tld_care') . $add_url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getMyMivitaPortalUrl($protocol = true){
|
||||
public static function getMyMivitaPortalUrl($protocol = true)
|
||||
{
|
||||
$pro = $protocol ? config('app.protocol') : "";
|
||||
return $pro.config('app.pre_url_portal').config('app.domain').config('app.tld_care');
|
||||
return $pro . config('app.pre_url_portal') . config('app.domain') . config('app.tld_care');
|
||||
}
|
||||
public static function getMyMivitaUrl($protocol = true){
|
||||
public static function getMyMivitaUrl($protocol = true)
|
||||
{
|
||||
$pro = $protocol ? config('app.protocol') : "";
|
||||
return $pro.config('app.pre_url_crm').config('app.domain').config('app.tld_care');
|
||||
return $pro . config('app.pre_url_crm') . config('app.domain') . config('app.tld_care');
|
||||
}
|
||||
|
||||
public static function getUserPaymentFor($instance = 'shopping'){
|
||||
if(Yard::instance($instance)->getYardExtra('user_shop_payment')){
|
||||
public static function getUserPaymentFor($instance = 'shopping')
|
||||
{
|
||||
if (Yard::instance($instance)->getYardExtra('user_shop_payment')) {
|
||||
return Yard::instance($instance)->getYardExtra('user_shop_payment');
|
||||
}
|
||||
if(\Session::has('user_shop_payment')){
|
||||
if (\Session::has('user_shop_payment')) {
|
||||
return \Session::get('user_shop_payment');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getUserShopBackUrl($reference = ""){
|
||||
public static function getUserShopBackUrl($reference = "")
|
||||
{
|
||||
|
||||
if(\Session::has('user_shop')){
|
||||
if(\Session::has('user_shop_domain')){
|
||||
if (\Session::has('user_shop')) {
|
||||
if (\Session::has('user_shop_domain')) {
|
||||
return \Session::get('user_shop_domain');
|
||||
}
|
||||
if($user_shop = \Session::get('user_shop')){
|
||||
return config('app.protocol').$user_shop->slug.".".config('app.domain').config('app.tld_care')."/back/to/shop/".$reference;
|
||||
if ($user_shop = \Session::get('user_shop')) {
|
||||
return config('app.protocol') . $user_shop->slug . "." . config('app.domain') . config('app.tld_care') . "/back/to/shop/" . $reference;
|
||||
}
|
||||
}
|
||||
return config('app.protocol').config('app.domain').config('app.tld_care');
|
||||
return config('app.protocol') . config('app.domain') . config('app.tld_care');
|
||||
}
|
||||
|
||||
public static function getUserCardBackUrl($uri, $instance = 'shopping'){
|
||||
public static function getUserCardBackUrl($uri, $instance = 'shopping')
|
||||
{
|
||||
|
||||
if(\Session::has('user_shop')){
|
||||
if(\Session::has('user_shop_domain')){
|
||||
if(\Session::has('back_link')){
|
||||
if (\Session::has('user_shop')) {
|
||||
if (\Session::has('user_shop_domain')) {
|
||||
if (\Session::has('back_link')) {
|
||||
return \Session::get('back_link');
|
||||
}
|
||||
if(self::getUserPaymentFor($instance) === 3){
|
||||
return \Session::get('user_shop_domain')."/user/membership";
|
||||
if (self::getUserPaymentFor($instance) === 3) {
|
||||
return \Session::get('user_shop_domain') . "/user/membership";
|
||||
}
|
||||
if(self::getUserPaymentFor($instance) === 2){
|
||||
return \Session::get('user_shop_domain')."/user/orders";
|
||||
if (self::getUserPaymentFor($instance) === 2) {
|
||||
return \Session::get('user_shop_domain') . "/user/orders";
|
||||
}
|
||||
return \Session::get('user_shop_domain');
|
||||
}
|
||||
if($user_shop = \Session::get('user_shop')){
|
||||
return config('app.protocol').$user_shop->slug.".".config('app.domain').config('app.tld_care').$uri;
|
||||
if ($user_shop = \Session::get('user_shop')) {
|
||||
return config('app.protocol') . $user_shop->slug . "." . config('app.domain') . config('app.tld_care') . $uri;
|
||||
}
|
||||
}
|
||||
return config('app.protocol').config('app.domain').config('app.tld_care');
|
||||
return config('app.protocol') . config('app.domain') . config('app.tld_care');
|
||||
}
|
||||
|
||||
public static function isMivitaShop(){
|
||||
if(Request::getHost() === 'checkout.'.config('app.domain').config('app.tld_care')){
|
||||
if($user_shop = \Session::get('user_shop')){
|
||||
if($user_shop->slug === 'aloevera' || $user_shop->slug === 'naturcosmetic'){
|
||||
public static function isMivitaShop()
|
||||
{
|
||||
if (Request::getHost() === 'checkout.' . config('app.domain') . config('app.tld_care')) {
|
||||
if ($user_shop = \Session::get('user_shop')) {
|
||||
if ($user_shop->slug === 'aloevera' || $user_shop->slug === 'naturcosmetic') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Request::getHost() === 'naturcosmetic.'.config('app.domain').config('app.tld_care')){
|
||||
if (Request::getHost() === 'naturcosmetic.' . config('app.domain') . config('app.tld_care')) {
|
||||
return true;
|
||||
}
|
||||
return \Config::get('app.url') === config('app.domain').config('app.tld_shop');
|
||||
}
|
||||
return \Config::get('app.url') === config('app.domain') . config('app.tld_shop');
|
||||
}
|
||||
|
||||
public static function isTestSystem($dev = false){
|
||||
if(\Config::get('app.tld_care') === '.test' || \Config::get('app.tld_shop') === '.lshop'){
|
||||
if($dev && config('app.debug') !== true){
|
||||
public static function isTestSystem($dev = false)
|
||||
{
|
||||
if (\Config::get('app.tld_care') === '.test' || \Config::get('app.tld_shop') === '.lshop') {
|
||||
if ($dev && config('app.debug') !== true) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -347,25 +380,61 @@ class Util
|
|||
return $size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function sanitize($string, $force_lowercase = true, $anal = false, $substr = false)
|
||||
{
|
||||
$strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
|
||||
"}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—",
|
||||
"—", "–", ",", "<", ".", ">", "/", "?");
|
||||
$strip = array(
|
||||
"~",
|
||||
"`",
|
||||
"!",
|
||||
"@",
|
||||
"#",
|
||||
"$",
|
||||
"%",
|
||||
"^",
|
||||
"&",
|
||||
"*",
|
||||
"(",
|
||||
")",
|
||||
"_",
|
||||
"=",
|
||||
"+",
|
||||
"[",
|
||||
"{",
|
||||
"]",
|
||||
"}",
|
||||
"\\",
|
||||
"|",
|
||||
";",
|
||||
":",
|
||||
"\"",
|
||||
"'",
|
||||
"‘",
|
||||
"’",
|
||||
"“",
|
||||
"”",
|
||||
"–",
|
||||
"—",
|
||||
"—",
|
||||
"–",
|
||||
",",
|
||||
"<",
|
||||
".",
|
||||
">",
|
||||
"/",
|
||||
"?"
|
||||
);
|
||||
$clean = trim(str_replace($strip, "", strip_tags($string)));
|
||||
$clean = preg_replace('/\s+/', "_", $clean);
|
||||
$clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ;
|
||||
|
||||
if($substr){
|
||||
$clean = (strlen($clean) > 20) ? substr($clean,-20) : $clean;
|
||||
$clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean;
|
||||
|
||||
if ($substr) {
|
||||
$clean = (strlen($clean) > 20) ? substr($clean, -20) : $clean;
|
||||
}
|
||||
return ($force_lowercase) ?
|
||||
(function_exists('mb_strtolower')) ?
|
||||
mb_strtolower($clean, 'UTF-8') :
|
||||
strtolower($clean) :
|
||||
mb_strtolower($clean, 'UTF-8') :
|
||||
strtolower($clean) :
|
||||
$clean;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ use Laravel\Passport\HasApiTokens;
|
|||
* @method static \Illuminate\Database\Eloquent\Builder|User wherePreSponsor($value)
|
||||
* @property \Illuminate\Support\Carbon|null $pre_deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePreDeletedAt($value)
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\UserBusiness> $userBusiness
|
||||
* @property-read int|null $user_business_count
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
|
|
|
|||
|
|
@ -129,62 +129,4 @@ if (! function_exists('legal_url')) {
|
|||
return url($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('main_asset')) {
|
||||
/**
|
||||
* Generate an asset URL using the main domain to avoid CORS issues.
|
||||
* This ensures all assets are loaded from the main domain regardless of subdomain.
|
||||
*/
|
||||
function main_asset($path)
|
||||
{
|
||||
// Entferne führende Slashes
|
||||
$path = ltrim($path, '/');
|
||||
|
||||
// Baue die Hauptdomain-URL
|
||||
$protocol = config('app.protocol', 'https://');
|
||||
$domain = config('app.domain', 'mivita');
|
||||
$tld = config('app.tld_care', '.care');
|
||||
|
||||
return $protocol . $domain . $tld . '/' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('cors_asset')) {
|
||||
/**
|
||||
* Alias for main_asset for backward compatibility and clarity.
|
||||
*/
|
||||
function cors_asset($path)
|
||||
{
|
||||
return main_asset($path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('route')) {
|
||||
/**
|
||||
* Route auf Hauptdomain generieren
|
||||
*/
|
||||
function route($name, $parameters = [], $absolute = true)
|
||||
{
|
||||
$url = route($name, $parameters, $absolute);
|
||||
|
||||
// Ersetze Subdomain mit Hauptdomain
|
||||
if (request()->hasHeader('X-Subdomain')) {
|
||||
$currentHost = request()->getHost();
|
||||
$url = str_replace($currentHost, config('app.domain').config('app.tld_care'), $url);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('asset_route')) {
|
||||
/**
|
||||
* Asset/Storage Route immer auf Hauptdomain
|
||||
*/
|
||||
function asset_route($name, $parameters = [])
|
||||
{
|
||||
return 'https://' . config('app.domain') . config('app.tld_care') . route($name, $parameters, false);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue