middleware('admin'); } public function index() { return view('admin.incentive.index'); } public function create() { return view('admin.incentive.create', [ 'languages' => config('localization.supportedLocales'), ]); } public function store() { $data = Request::validate([ 'name' => 'required|string|max:255', 'subtitle' => 'nullable|string|max:255', 'description' => 'nullable|string', 'image' => 'nullable|string|max:255', 'terms' => 'nullable|string', 'qualification_start' => 'required|date', 'qualification_end' => 'required|date|after_or_equal:qualification_start', 'calculation_end' => 'required|date|after_or_equal:qualification_end', 'points_partner_onetime' => 'required|integer|min:0', 'points_abo_onetime' => 'required|integer|min:0', 'min_direct_partners' => 'required|integer|min:0', 'min_customer_abos' => 'required|integer|min:0', 'max_winners' => 'required|integer|min:1', 'status' => 'required|integer|in:0,1,2', ]); $data = array_merge($data, $this->extractTranslations()); Incentive::create($data); \Session()->flash('alert-success', __('incentive.created')); return redirect(route('admin_incentives')); } public function show($id) { $incentive = Incentive::findOrFail($id); $participants = IncentiveParticipant::where('incentive_id', $incentive->id) ->with('user', 'user.account') ->orderByIncentiveLeaderboard() ->get(); return view('admin.incentive.show', [ 'incentive' => $incentive, 'participants' => $participants, ]); } public function edit($id) { $incentive = Incentive::findOrFail($id); return view('admin.incentive.edit', [ 'incentive' => $incentive, 'languages' => config('localization.supportedLocales'), ]); } public function update($id) { $data = Request::validate([ 'name' => 'required|string|max:255', 'subtitle' => 'nullable|string|max:255', 'description' => 'nullable|string', 'image' => 'nullable|string|max:255', 'terms' => 'nullable|string', 'qualification_start' => 'required|date', 'qualification_end' => 'required|date|after_or_equal:qualification_start', 'calculation_end' => 'required|date|after_or_equal:qualification_end', 'points_partner_onetime' => 'required|integer|min:0', 'points_abo_onetime' => 'required|integer|min:0', 'min_direct_partners' => 'required|integer|min:0', 'min_customer_abos' => 'required|integer|min:0', 'max_winners' => 'required|integer|min:1', 'status' => 'required|integer|in:0,1,2', ]); $data = array_merge($data, $this->extractTranslations()); $incentive = Incentive::findOrFail($id); $incentive->update($data); \Session()->flash('alert-success', __('incentive.updated')); return redirect(route('admin_incentive_show', [$id])); } /** * @return array{trans_name: array, trans_description: array, trans_terms: array} */ private function extractTranslations(): array { $transName = []; $transDescription = []; $transTerms = []; $transSubtitle = []; foreach (config('localization.supportedLocales') as $locale => $localeData) { if ($locale !== 'de') { $transName[$locale] = Request::get('trans_name_'.$locale, ''); $transSubtitle[$locale] = Request::get('trans_subtitle_'.$locale, ''); $transDescription[$locale] = Request::get('trans_description_'.$locale, ''); $transTerms[$locale] = Request::get('trans_terms_'.$locale, ''); } } return [ 'trans_name' => $transName, 'trans_subtitle' => $transSubtitle, 'trans_description' => $transDescription, 'trans_terms' => $transTerms, ]; } public function recalculate($id) { $incentive = Incentive::findOrFail($id); $service = new IncentiveCalculationService; $stats = $service->recalculate($incentive, Request::has('force')); \Session()->flash('alert-success', __('incentive.recalculated', [ 'participants' => $stats['participants'], 'errors' => $stats['errors'], ])); return redirect(route('admin_incentive_show', [$id])); } public function participantDetails($participant_id) { $participant = IncentiveParticipant::with('incentive', 'user', 'user.account') ->findOrFail($participant_id); $data = self::buildParticipantDetailData($participant); return view('admin.incentive._participant_details', $data); } /** * Baut die Detail-Daten fuer einen Teilnehmer auf. * Wird von Admin und User Controller genutzt. */ public static function buildParticipantDetailData(IncentiveParticipant $participant): array { $incentive = $participant->incentive; $calculation_months = $incentive->getCalculationMonths(); // Alle Logs dieses Teilnehmers (ohne Stornos) $all_logs = IncentivePointsLog::where('participant_id', $participant->id) ->where('is_storno', false) ->with('salesVolume') ->orderBy('created_at') ->get(); // UserSalesVolume-IDs -> User-ID Mapping aufbauen (fuer akkumulierte Partner-Punkte) $sv_ids = $all_logs->whereNotNull('user_sales_volume_id')->pluck('user_sales_volume_id')->unique()->toArray(); $sv_user_map = ! empty($sv_ids) ? UserSalesVolume::whereIn('id', $sv_ids)->pluck('user_id', 'id')->toArray() : []; // Partner aus Tracking-Tabelle $new_partners = IncentiveNewPartner::where('participant_id', $participant->id) ->with('user', 'user.account') ->orderBy('registered_at') ->get(); $partner_logs = $all_logs->where('type', 'partner'); $partner_sources = $new_partners->map(function ($np) use ($partner_logs, $sv_user_map, $calculation_months, $incentive) { $monthly = []; $transactions = []; foreach ($calculation_months as $period) { // Akkumulierte Logs: source_type=UserSalesVolume, deren USV.user_id == partner user_id $month_logs = $partner_logs ->where('month', $period['month']) ->where('year', $period['year']) ->filter(function ($log) use ($np, $sv_user_map) { if ($log->source_type === User::class) { return false; // Einmal-Punkte nicht in Monatsspalte } if ($log->incentive_new_partner_id) { return (int) $log->incentive_new_partner_id === (int) $np->id; } // Legacy: USV.user_id muss zum Partner gehoeren return isset($sv_user_map[$log->user_sales_volume_id]) && $sv_user_map[$log->user_sales_volume_id] === $np->user_id; }); $month_points = (int) $month_logs->sum('points_accumulated'); $monthly[] = $month_points; foreach ($month_logs as $log) { $transactions[] = [ 'date' => $log->created_at->format('d.m.Y'), 'month' => $period['month'], 'year' => $period['year'], 'label' => $log->source_label ?: 'KP #'.($log->user_sales_volume_id ?? $log->source_id), 'points' => $log->points_accumulated, 'type' => 'accumulated', ]; } } // Einmal-Punkte als Transaktion hinzufuegen $onetime_log = $partner_logs ->where('source_type', User::class) ->first(function ($log) use ($np) { if ($log->incentive_new_partner_id) { return (int) $log->incentive_new_partner_id === (int) $np->id; } return (int) $log->source_id === (int) $np->user_id; }); if ($onetime_log) { array_unshift($transactions, [ 'date' => $onetime_log->created_at->format('d.m.Y'), 'month' => $onetime_log->month, 'year' => $onetime_log->year, 'label' => __('incentive.onetime_registration'), 'points' => $onetime_log->points_onetime, 'type' => 'onetime', ]); } return [ 'id' => $np->id, 'label' => $np->user ? ($np->user->getFullName() ?: $np->user->email ?: 'User #'.$np->user_id) : 'User #'.$np->user_id, 'month' => $np->registered_at->month, 'year' => $np->registered_at->year, 'onetime' => $incentive->points_partner_onetime, 'monthly' => $monthly, 'total' => $incentive->points_partner_onetime + array_sum($monthly), 'transactions' => $transactions, ]; }); // Abos aus Tracking-Tabelle $new_abos = IncentiveNewAbo::where('participant_id', $participant->id) ->with('userAbo') ->orderBy('activated_at') ->get(); $abo_logs = $all_logs->where('type', 'abo'); // Legacy-Fallback: USV -> user_abo_id (Logs ohne incentive_new_abo_id) $sv_user_abo_map = []; $needs_legacy_abo_map = $abo_logs->whereNull('incentive_new_abo_id')->whereNotNull('user_sales_volume_id')->isNotEmpty(); if ($needs_legacy_abo_map && ! empty($sv_ids)) { $sv_rows = UserSalesVolume::query() ->whereIn('id', $sv_ids) ->whereNotNull('shopping_order_id') ->get(['id', 'shopping_order_id']); $order_ids = $sv_rows->pluck('shopping_order_id')->unique()->filter()->values(); if ($order_ids->isNotEmpty()) { $user_abo_id_by_order_id = UserAboOrder::query() ->whereIn('shopping_order_id', $order_ids) ->get(['shopping_order_id', 'user_abo_id']) ->keyBy('shopping_order_id'); foreach ($sv_rows as $sv) { $link = $user_abo_id_by_order_id->get($sv->shopping_order_id); if ($link) { $sv_user_abo_map[$sv->id] = (int) $link->user_abo_id; } } } } $abo_sources = $new_abos->map(function ($na) use ($abo_logs, $sv_user_abo_map, $calculation_months, $incentive) { $monthly = []; $transactions = []; $tracked_user_abo_id = (int) $na->user_abo_id; foreach ($calculation_months as $period) { $month_logs = $abo_logs ->where('month', $period['month']) ->where('year', $period['year']) ->filter(function ($log) use ($na, $tracked_user_abo_id, $sv_user_abo_map) { if ($log->source_type !== UserSalesVolume::class) { return false; } if ($log->incentive_new_abo_id) { return (int) $log->incentive_new_abo_id === (int) $na->id; } $usv_id = $log->user_sales_volume_id; if (! $usv_id || ! isset($sv_user_abo_map[$usv_id])) { return false; } return $sv_user_abo_map[$usv_id] === $tracked_user_abo_id; }); $month_points = (int) $month_logs->sum('points_accumulated'); $monthly[] = $month_points; foreach ($month_logs as $log) { $transactions[] = [ 'date' => $log->created_at->format('d.m.Y'), 'month' => $period['month'], 'year' => $period['year'], 'label' => $log->source_label ?: 'SV #'.($log->user_sales_volume_id ?? $log->source_id), 'points' => $log->points_accumulated, 'type' => 'accumulated', ]; } } // Einmal-Punkte als Transaktion $onetime_log = $abo_logs ->where('source_type', '!=', UserSalesVolume::class) ->first(function ($log) use ($na) { if ($log->incentive_new_abo_id) { return (int) $log->incentive_new_abo_id === (int) $na->id; } return (int) $log->source_id === (int) $na->user_abo_id; }); if ($onetime_log) { array_unshift($transactions, [ 'date' => $onetime_log->created_at->format('d.m.Y'), 'month' => $onetime_log->month, 'year' => $onetime_log->year, 'label' => __('incentive.onetime_abo_activation'), 'points' => $onetime_log->points_onetime, 'type' => 'onetime', ]); } $label = $na->userAbo?->email ?: ('Abo #'.$na->user_abo_id); return [ 'id' => $na->id, 'label' => $label, 'month' => $na->activated_at->month, 'year' => $na->activated_at->year, 'onetime' => $incentive->points_abo_onetime, 'monthly' => $monthly, 'total' => $incentive->points_abo_onetime + array_sum($monthly), 'transactions' => $transactions, ]; }); return [ 'incentive' => $incentive, 'participant' => $participant, 'calculation_months' => $calculation_months, 'partner_sources' => $partner_sources, 'abo_sources' => $abo_sources, ]; } public function datatable() { $query = Incentive::query()->select('incentives.*'); return \DataTables::eloquent($query) ->addColumn('action', function (Incentive $incentive) { return ' '; }) ->addColumn('status_label', function (Incentive $incentive) { return ''.$incentive->getStatusType().''; }) ->addColumn('period', function (Incentive $incentive) { return $incentive->qualification_start->format('d.m.Y').' - '.$incentive->qualification_end->format('d.m.Y'); }) ->addColumn('participants_count', function (Incentive $incentive) { return $incentive->participants()->count(); }) ->orderColumn('name', 'name $1') ->orderColumn('status_label', 'status $1') ->rawColumns(['action', 'status_label']) ->make(true); } }