mivita/app/Http/Controllers/ModalController.php
Kevin ac3bd6e8ea Statistiken/Abo: Kundenfeedback 2026-07-24 komplett umgesetzt + Steuern: DATEV-Fixes 08+09/2025 (Sammel-Commit Branch-WIP)
Feedback-Umsetzung (dev/2026-07-22/feedback-analyse-2026-07-24.md, alle 10 Punkte):
- VIP-Statistik: eigener Berater ('ich', Linie 0) in Neue-Kundenabos- und beiden Top-10-Listen; Top-10-Umsatz sortiert nach abgerechneten Ist-Punkten + Abo-Anzahl-Spalte; Fußnote nicht-payline-wirksame Punkte (SalesVolumePoints::nonPaylinePoints)
- Führungskräfte-Liste: beide Sichten 'direkt' (an nächster FK abgeschnitten, additiv) und 'gesamt' (komplette Downline), Gesamt-Spalte nach Kundenzählweise
- Team-Abos: je Ebene 'abgerechnet (Monat)' und 'bis zum 25. erwartet'; Payline-Karte mit Teamabo-/Kundenabo-Zählern
- Incentive-Live-Ranking: Qualifizierte zuerst nummeriert (fixt auch isWinner rank<=max_winners)
- Porto: Netto-/Brutto-Kennzeichnung (Berater netto, Endkunden inkl. MwSt., portal_view-Flag; auch CRM-/Shop-Warenkorb + Checkout), Yard::shipping_tax-Fix
- Struktur-Ansicht: Abo-Badges 'A' (eigenes Abo) und '(n)' (Kundenabos) je Teampartner, gebatcht
- Abo-Löschung: Softdelete setzt active=0 (UserAbo deleting-Hook) + Bereinigungs-Migration für gelöschte-aber-aktive Abos
- Doku: technische Analyse + Kundinnen-Feedback-Dokument unter dev/2026-07-22/
- Tests: IncentiveRankingTest, TeamStructureAboBadgesTest, YardShippingTaxTest (neu); BackofficeStatisticsListsTest, TeamAboOverviewTest, PaylineForecastServiceTest, AboAdminDeleteTest, AboOneTimeViewTest erweitert

Außerdem enthalten (Branch-WIP vor dem Feedback): DATEV-Abgleich-Fixes 08+09/2025, USt-ID-Live-Validierung sowie restliche unkommittete Stände (u. a. Abo-Pause-Sprachkeys).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:56:02 +00:00

308 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Country;
use App\Models\Homeparty;
use App\Models\HomepartyUser;
use App\Models\Product;
use App\Models\ShoppingOrder;
use App\Models\ShoppingUser;
use App\Models\UserAbo;
use App\Models\UserCredit;
use App\Models\UserLevel;
use App\Models\UserSalesVolume;
use App\Services\Abo\TeamAboOverviewService;
use App\Services\AboHelper;
use App\Services\BusinessPlan\TreeCalcBot;
use App\Services\BusinessPlan\TreeCalcBotOptimized;
use App\Services\DhlModalService;
use App\Services\DhlProductResolver;
use App\User;
use Request;
class ModalController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function load()
{
$data = Request::all();
$ret = '';
$status = false;
if (Request::ajax()) {
if ($data['action'] === 'shopping-order-change-member') {
$value = ShoppingOrder::find($data['id']);
$route = route('admin_sales_customers_detail', [$value->id]);
$ret = view('admin.modal.member', compact('value', 'data', 'route'))->render();
}
if ($data['action'] === 'shopping-user-change-member') {
$value = ShoppingUser::find($data['id']);
$route = route('admin_customer_edit', [$value->id]);
$ret = view('admin.modal.member', compact('value', 'data', 'route'))->render();
}
if ($data['action'] === 'shopping-user-is-like-member') {
$current = ShoppingUser::find($data['id']); // current user form order
$possibles = [];
if ($current->is_like) {
$likes = $current->getNotice('like');
foreach ($likes as $like_id => $number) {
$possibles[] = ShoppingUser::find($like_id);
}
}
$ret = view('admin.modal.is_like_member', compact('current', 'possibles', 'data'))->render();
}
if ($data['action'] === 'shopping-order-change-points') {
$value = ShoppingOrder::find($data['id']);
$route = route('admin_sales_customers_detail', [$value->id]);
$ret = view('admin.modal.change_points', compact('value', 'data', 'route'))->render();
}
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();
}
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();
}
if ($data['action'] === 'shop-user-order-detail') {
$user = \Auth::user();
$shopping_order = ShoppingOrder::findOrFail($data['id']);
if (! $user->isAdmin() && $shopping_order->member_id !== $user->id) {
abort(404);
}
$isAdmin = false;
$ret = view('user.shop.sales.modal_api_order_detail', compact('shopping_order', 'isAdmin', 'data'))->render();
}
if ($data['action'] === 'shop-user-order-shipping-detail') {
$user = \Auth::user();
$shopping_order = ShoppingOrder::findOrFail($data['id']);
if (! $user->isAdmin() && $shopping_order->auth_user_id !== $user->id) {
abort(404);
}
$isAdmin = false;
$ret = view('user.shop.sales.modal_api_order_shipping_detail', compact('shopping_order', 'isAdmin', 'data'))->render();
}
if ($data['action'] === 'user-order-dhl-info') {
$user = \Auth::user();
$shopping_order = ShoppingOrder::with('dhlShipments')->findOrFail($data['id']);
if (! $user->isAdmin() && $shopping_order->auth_user_id !== $user->id && $shopping_order->member_id !== $user->id) {
abort(404);
}
$isAdmin = false;
$ret = view('user.order.modal_dhl_shipping_info', compact('shopping_order', 'isAdmin'))->render();
}
if ($data['action'] === 'user-order-my-delivery-show') {
$user = \Auth::user();
$ret = view('admin.modal.show_user_customers', compact('user', 'data'))->render();
}
if ($data['action'] === 'user-order-my-delivery-add') {
$user = \Auth::user();
/* $product = Product::find($data['id']); //current user form order
$ret = view("admin.modal.show_product", compact('product', 'data'))->render(); */
}
if ($data['action'] === 'homeparty-add-product') {
$homeparty = Homeparty::find($data['id']);
$homeparty_user = HomepartyUser::find($data['user_id']);
$data['homeparty'] = $homeparty;
$ret = view('user.homeparty.modal_hp_show_products', compact('data', 'homeparty', 'homeparty_user'))->render();
}
if ($data['action'] === 'user-level-edit') {
$value = UserLevel::find($data['id']);
$route = route('admin_level_store', [$value->id]);
$ret = view('admin.modal.user_level_edit', compact('value', 'data', 'route'))->render();
}
if ($data['action'] === 'user-level-add') {
$value = new UserLevel;
$route = route('admin_level_store', ['new']);
$ret = view('admin.modal.user_level_edit', compact('value', 'data', 'route'))->render();
}
if ($data['action'] === 'business-user-detail') {
$user = User::findOrFail($data['id']);
if ($data['init_from'] === 'admin') {
$data['month'] = session('business_user_filter_month');
$data['year'] = session('business_user_filter_year');
} else {
$data['month'] = session('team_user_filter_month');
$data['year'] = session('team_user_filter_year');
}
$data['live'] = $data['live'] ?? false;
$data['optimized'] = $data['optimized'] ?? false;
$TreeCalcBot = $this->getForBusinessUserDetail($user, $data);
$route = '';
$ret = view('admin.modal.business_user_detail', compact('TreeCalcBot', 'user', 'data'))->render();
}
if ($data['action'] === 'team-abo-member-show') {
$summary = app(TeamAboOverviewService::class)
->memberAboSummary(\Auth::user(), (int) $data['id']);
if ($summary) {
$ret = view('user.team.modal_member_abos', compact('summary', 'data'))->render();
} else {
$ret = view('admin.modal.business_user_notfound', compact('data'))->render();
}
}
if ($data['action'] === 'business-user-show') {
$user = User::with(['account', 'account.country', 'account.shipping_country', 'user_level', 'user_sponsor.account'])->find($data['id']);
\Log::info('business-user-show', ['user' => $user]);
if ($user) {
$route = '';
$ret = view('admin.modal.business_user_show', compact('user', 'data'))->render();
} else {
$ret = view('admin.modal.business_user_notfound', compact('data'))->render();
}
}
if ($data['action'] === 'edit_user_sales_volume') {
$userSalesVolume = UserSalesVolume::findOrFail($data['id']);
$route = route('admin_business_points_store');
$ret = view('admin.business.modal_edit_points', compact('userSalesVolume', 'data', 'route'))->render();
}
if ($data['action'] === 'add_user_sales_volume') {
$userSalesVolume = new UserSalesVolume;
$route = route('admin_business_points_store');
$ret = view('admin.business.modal_add_points', compact('userSalesVolume', 'data', 'route'))->render();
}
if ($data['action'] === 'add-user-credit') {
$value = [];
$ret = view('admin.payment.modal_add_credit', compact('value', 'data'))->render();
}
if ($data['action'] === 'user-credit-status') {
$UserCredit = UserCredit::findOrFail($data['id']); // current user form order
$ret = view('admin.payment.modal_credit_status', compact('UserCredit', 'data'))->render();
}
if ($data['action'] === 'abo_update_settings') {
$user_abo = UserAbo::find($data['id']);
if ($data['view'] === 'admin') {
$route = route('admin_abos_update', [$user_abo->id]);
} else {
$route = route('user_abos_update', [$data['view'], $user_abo->id]);
}
$ret = view('admin.abo.modal_abo_update', compact('user_abo', 'data', 'route'))->render();
}
if ($data['action'] === 'abo-add-product') {
$user_abo = UserAbo::find($data['id']);
$ret = view('user.abo.modal_abo_show_products', compact('data', 'user_abo'))->render();
}
if ($data['action'] === 'abo-add-onetime') {
$user_abo = UserAbo::find($data['id']);
abort_unless($user_abo && AboHelper::isOneTimeFeatureVisible($user_abo), 403);
$ret = view('user.abo.modal_abo_onetime_products', compact('data', 'user_abo'))->render();
}
if ($data['action'] === 'create-dhl-shipment') {
$this->authorizeDhlShipmentModal();
$id = $data['id'] ?? null;
$ret = $this->handleDhlShipmentModal($id, $data);
}
}
return response()->json(['response' => $data, 'html' => $ret, 'status' => $status]);
}
private function getForBusinessUserDetail(User $user, $data)
{
// $auth_user = \Auth::user();
// if($auth_user->isAdmin() || $auth_user->id === $user->id){
if ($data['optimized']) {
$TreeCalcBot = new TreeCalcBotOptimized($data['month'], $data['year'], $data['init_from'], $data['live']);
} else {
$TreeCalcBot = new TreeCalcBot($data['month'], $data['year'], $data['init_from']);
}
$TreeCalcBot->initBusinesslUserDetail($user, $data['live']);
// TODO is not Admin, read is user in Parent tree ...
if (! $TreeCalcBot->business_user) {
abort(403, 'no user found');
}
return $TreeCalcBot;
// }
return null;
}
/**
* Ensure the current user is allowed to use the DHL shipment modal.
*
* The DHL cockpit is an admin-only tool. Without this guard a logged-in
* CRM user could call `POST /modal/load` with `action=create-dhl-shipment`
* and an arbitrary order id and would receive that order's recipient
* name, address, e-mail and existing shipments (IDOR).
*/
private function authorizeDhlShipmentModal(): void
{
$user = \Auth::user();
if (! $user || ! method_exists($user, 'isAdmin') || ! $user->isAdmin()) {
abort(403, 'DHL shipment modal is only available for admin users.');
}
}
/**
* 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' => Country::where('active', 1)->get(),
'productCodes' => [
'V01PAK' => 'DHL Paket (National)',
'V53WPAK' => 'DHL Paket International',
],
'productSuggestions' => (new DhlProductResolver)->getProductSuggestionsByCountry(),
'selectedProductCode' => 'V01PAK',
'errors' => ['Fehler beim Laden der Daten: '.$e->getMessage()],
'warnings' => [],
];
return view('admin.dhl.modal_create_shipment', $errorData)->render();
}
}
}
/* <button type="button" class="btn btn-sm btn-primary" data-toggle="modal"
data-target="#modals-load-content"
data-id="{{ $value->id }}"
data-model="emailTemplate"
data-action="modal-email-template"
data-url=""
data-redirect="back"
data-route="{{ route('modal_load') }}"><span class="fa fa-edit"></span></button>*/