diff --git a/app/Console/Commands/CheckPaymentsAccount.php b/app/Console/Commands/CheckPaymentsAccount.php
new file mode 100644
index 00000000..64ed689a
--- /dev/null
+++ b/app/Console/Commands/CheckPaymentsAccount.php
@@ -0,0 +1,213 @@
+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;
+ }
+}
\ No newline at end of file
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index ce5cf2ca..2275555e 100755
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -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');
}
/**
diff --git a/app/Http/Controllers/DhlShipmentController.php b/app/Http/Controllers/DhlShipmentController.php
new file mode 100644
index 00000000..0bcb159b
--- /dev/null
+++ b/app/Http/Controllers/DhlShipmentController.php
@@ -0,0 +1,662 @@
+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 '';
+ })
+ ->editColumn('id', function ($shipment) {
+ return '#' . $shipment->id . '';
+ })
+ ->addColumn('type', function ($shipment) {
+ if ($shipment->type == 'outbound') {
+ return ' Ausgehend';
+ } else {
+ return ' Retoure';
+ }
+ })
+ ->addColumn('order', function ($shipment) {
+ if ($shipment->order_id) {
+ return '#' . $shipment->order_id . '';
+ }
+ return 'N/A';
+ })
+ ->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) .
+ '
' . e($shipment->shoppingOrder->shopping_user->billing_email) . '';
+ }
+ return 'Unbekannt';
+ })
+ ->editColumn('dhl_shipment_no', function ($shipment) {
+ return $shipment->dhl_shipment_no ? '' . e($shipment->dhl_shipment_no) . '' : '-';
+ })
+ ->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 '' . $statusInfo['text'] . '';
+ })
+ ->addColumn('tracking_status', function ($shipment) {
+ if ($shipment->tracking_status) {
+ return '' . e($shipment->tracking_status) . '' .
+ ($shipment->last_tracked_at ? '
' . $shipment->last_tracked_at->format('d.m.Y H:i') . '' : '');
+ }
+ return '-';
+ })
+ ->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 = '
+ Erstellen Sie eine neue DHL-Sendung basierend auf einer bestehenden Bestellung. +
+ ++ Suchen und anzeigen einer bestimmten Sendung nach ID oder Tracking-Nummer. +
+
+ Überprüfen Sie, ob die in der .env-Datei hinterlegten DHL-Zugangsdaten korrekt sind.
+
| + + | +# | +Typ | +Bestellung | +Kunde | +Sendungsnummer | +Status | +Tracking-Status | +Gewicht | +Erstellt | +Aktionen | +
|---|
| Bestellungs-ID: | +#{{ $order->id }} | +
| Kunde: | +
+ {{ $order->user->first_name ?? '' }} {{ $order->user->last_name ?? '' }}
+ + {{ $order->user->email ?? '' }} + |
+
| Bestelldatum: | +{{ $order->created_at->format('d.m.Y H:i') }} | +
| Bestellwert: | +{{ number_format($order->order_total, 2) }} € | +
| Lieferadresse: | +
+ {{ $order->shipping_first_name ?? $order->user->first_name }} {{ $order->shipping_last_name ?? $order->user->last_name }} + {{ $order->shipping_street ?? $order->user->street }} {{ $order->shipping_house_number ?? $order->user->house_number }} + {{ $order->shipping_postcode ?? $order->user->postcode }} {{ $order->shipping_city ?? $order->user->city }} + {{ $order->shippingCountry->name ?? 'Deutschland' }} + |
+
| Status: | ++ + {{ $order->status }} + + | +
| Artikel: | +{{ $order->items->count() }} Artikel | +
| Artikel | +Menge | +Einzelpreis | +Gesamtpreis | +Gewicht (kg) | +
|---|---|---|---|---|
|
+ {{ $item->product->name ?? 'Unbekanntes Produkt' }}
+ @if($item->product->number)
+ + Art.-Nr: {{ $item->product->number }} + @endif + |
+ {{ $item->qty }} | +{{ number_format($item->price, 2) }} € | +{{ number_format($item->price * $item->qty, 2) }} € | +{{ number_format($itemWeight, 3) }} | +
| Geschätztes Gesamtgewicht: | +{{ number_format($totalWeight, 3) }} kg | +|||
{{ $shipment->shipment_number }}
+ @else
+ Nicht verfügbar
+ @endif
+ {{ $shipment->tracking_number }}
+ | ID: | +#{{ $shipment->id }} | +
| Typ: | +
+ @if($shipment->type == 'outbound')
+
+ Ausgehend
+
+ @else
+
+ Retoure
+
+ @if($shipment->relatedShipment)
+ + + Bezieht sich auf Sendung + + #{{ $shipment->relatedShipment->id }} + + + @endif + @endif + |
+
| Produktcode: | +{{ $shipment->product_code }} |
+
| Label-Format: | +{{ strtoupper($shipment->label_format) }} | +
| Label gedruckt: | ++ @if($shipment->label_printed) + Ja + @else + Nein + @endif + | +
| Erstellt: | +{{ $shipment->created_at->format('d.m.Y H:i:s') }} | +
| Letzte Änderung: | +{{ $shipment->updated_at->format('d.m.Y H:i:s') }} | +
| Letztes Tracking: | +{{ $shipment->last_tracked_at->format('d.m.Y H:i:s') }} | +
| Abmessungen: | ++ {{ $shipment->length }}×{{ $shipment->width }}×{{ $shipment->height }} cm + | +
| Bestellungs-ID: | ++ + #{{ $shipment->shoppingOrder->id }} + + | +
| Kunde: | +
+ @if($shipment->shoppingOrder->shopping_user)
+ {{ $shipment->shoppingOrder->shopping_user->billing_firstname }} {{ $shipment->shoppingOrder->shopping_user->billing_lastname }}
+ + {{ $shipment->shoppingOrder->shopping_user->billing_email }} + @else + Unbekannt + @endif + |
+
| Bestelldatum: | +{{ $shipment->shoppingOrder->created_at->format('d.m.Y H:i') }} | +
| Bestellwert: | +{{ number_format($shipment->shoppingOrder->total, 2) }} € | +
| Status: | ++ + {{ $shipment->shoppingOrder->status }} + + | +
| Artikel: | +{{ $shipment->shoppingOrder->shopping_order_items->count() ?? 0 }} Artikel | +
{{ json_encode($shipment->api_response_data, JSON_PRETTY_PRINT) }}
+ {{ __('Page not available') }}
- {{ __('back to the homepage') }} + {{ __('back to the homepage') }} diff --git a/resources/views/status/status_register.blade.php b/resources/views/status/status_register.blade.php index 8010456e..781e75fb 100755 --- a/resources/views/status/status_register.blade.php +++ b/resources/views/status/status_register.blade.php @@ -33,7 +33,7 @@{{ __('Thank you for your registration!') }}
{{ __('We have sent you an e-mail with a link to activate your data.') }}
- {{ __('back to the homepage') }} + {{ __('back to the homepage') }} diff --git a/resources/views/status/status_verify.blade.php b/resources/views/status/status_verify.blade.php index 59891e36..58568e16 100755 --- a/resources/views/status/status_verify.blade.php +++ b/resources/views/status/status_verify.blade.php @@ -33,7 +33,7 @@{{ __('Now assign a password.') }}
{{ __('create new password') }}
{{ __('shop.not_available_copy') }} - - - -
- -
+ {!! __('shop.open_copy_1') !!}
+
+ {!! __('shop.open_copy_2') !!}
+
+ {{ __('shop.open_note_hl') }}
+ {!! __('shop.open_note_1') !!}
+
- {{ __('shop.open_copy_1') }}
+ {!! __('shop.open_copy_1') !!}
- {{ __('shop.open_copy_2') }}
+ {!! __('shop.open_copy_2') !!}
{{ __('shop.open_note_hl') }}
{{ __('shop.open_note_1') }}
diff --git a/resources/views/user/shop_edit_name.blade.php b/resources/views/user/shop_edit_name.blade.php
new file mode 100644
index 00000000..b777f9ed
--- /dev/null
+++ b/resources/views/user/shop_edit_name.blade.php
@@ -0,0 +1,23 @@
+@extends('layouts.layout-2')
+
+@section('content')
+
+ {{-- @if ($errors->any())
+