411 lines
16 KiB
PHP
411 lines
16 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Incentive;
|
|
use App\Models\IncentiveNewAbo;
|
|
use App\Models\IncentiveNewPartner;
|
|
use App\Models\IncentiveParticipant;
|
|
use App\Models\IncentivePointsLog;
|
|
use App\Models\UserAboOrder;
|
|
use App\Models\UserSalesVolume;
|
|
use App\Services\Incentive\IncentiveCalculationService;
|
|
use App\User;
|
|
use Request;
|
|
|
|
class IncentiveController extends Controller
|
|
{
|
|
public function __construct()
|
|
{
|
|
$this->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<string, string>, trans_description: array<string, string>, trans_terms: array<string, string>}
|
|
*/
|
|
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 '<a href="'.route('admin_incentive_show', [$incentive->id]).'" class="btn icon-btn btn-sm btn-primary"><span class="fa fa-eye"></span></a>
|
|
<a href="'.route('admin_incentive_edit', [$incentive->id]).'" class="btn icon-btn btn-sm btn-warning"><span class="fa fa-edit"></span></a>';
|
|
})
|
|
->addColumn('status_label', function (Incentive $incentive) {
|
|
return '<span class="badge badge-'.$incentive->getStatusColor().'">'.$incentive->getStatusType().'</span>';
|
|
})
|
|
->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);
|
|
}
|
|
}
|