Incentive (E1): Recruiting-Challenge mit Geldpraemie, Auszahlungs-Export und parallelen Incentives
Neue Felder amount_per_partner, bonus_rank_1..3 und include_sales_points an incentives. Mit ausgeschalteten Umsatzpunkten zaehlt die Wertung nur die Einmalpunkte je Neupartner, die Rangliste entspricht damit der Anzahl Neupartner mit Starterpaket. IncentivePayoutService rechnet Neupartner mal Betrag plus Rang-Bonus fuer qualifizierte Gewinner; Admin-Detailseite bekommt Praemienspalte, Summen und XLSX-Export fuer die Auszahlung. Sidenav und Dashboard zeigen alle aktiven Incentives, damit Montenegro und die Challenge parallel laufen koennen. Teaser, Rangliste und Details zeigen bei Geldpraemie die Praemien-Kachel und "Deine Praemie bisher" und blenden Abo-Bloecke aus, wenn Abos keine Rolle spielen. Montenegro bleibt unveraendert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
03416b0afe
commit
e3efa2f5f0
27 changed files with 1418 additions and 129 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Exports\xExport;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewAbo;
|
||||
|
|
@ -12,8 +13,11 @@ use App\Models\UserAbo;
|
|||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Services\Incentive\IncentiveCalculationService;
|
||||
use App\Services\Incentive\IncentivePayoutService;
|
||||
use App\User;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Request;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class IncentiveController extends Controller
|
||||
{
|
||||
|
|
@ -50,8 +54,14 @@ class IncentiveController extends Controller
|
|||
'min_direct_partners' => 'required|integer|min:0',
|
||||
'min_customer_abos' => 'required|integer|min:0',
|
||||
'max_winners' => 'required|integer|min:1',
|
||||
'amount_per_partner' => 'nullable|numeric|min:0|max:999999',
|
||||
'bonus_rank_1' => 'nullable|numeric|min:0|max:999999',
|
||||
'bonus_rank_2' => 'nullable|numeric|min:0|max:999999',
|
||||
'bonus_rank_3' => 'nullable|numeric|min:0|max:999999',
|
||||
'include_sales_points' => 'nullable|boolean',
|
||||
'status' => 'required|integer|in:0,1,2',
|
||||
]);
|
||||
$data = $this->normalizePayoutFields($data);
|
||||
|
||||
$data = array_merge($data, $this->extractTranslations());
|
||||
|
||||
|
|
@ -70,12 +80,52 @@ class IncentiveController extends Controller
|
|||
->orderByIncentiveLeaderboard()
|
||||
->get();
|
||||
|
||||
$payoutService = new IncentivePayoutService;
|
||||
$payoutByParticipantId = $incentive->hasCashPayout()
|
||||
? $participants->mapWithKeys(fn (IncentiveParticipant $participant) => [
|
||||
$participant->id => $payoutService->forParticipant($incentive, $participant),
|
||||
])
|
||||
: collect();
|
||||
|
||||
return view('admin.incentive.show', [
|
||||
'incentive' => $incentive,
|
||||
'participants' => $participants,
|
||||
'payoutByParticipantId' => $payoutByParticipantId,
|
||||
'payoutTotals' => $incentive->hasCashPayout()
|
||||
? $payoutService->totals($payoutService->rows($incentive))
|
||||
: null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auszahlungsliste der Geldpraemie als XLSX (Februar-Auszahlung der Recruiting-Challenge).
|
||||
*/
|
||||
public function payoutExport($id): BinaryFileResponse
|
||||
{
|
||||
$incentive = Incentive::findOrFail($id);
|
||||
$service = new IncentivePayoutService;
|
||||
|
||||
$filename = 'incentive-'.$incentive->slug.'-auszahlung-'.now()->format('Y-m-d').'.xlsx';
|
||||
|
||||
return Excel::download(new xExport($service->exportRows($incentive), $service->exportHeaders()), $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leere Betragsfelder als NULL speichern, Checkbox als bool.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalizePayoutFields(array $data): array
|
||||
{
|
||||
foreach (['amount_per_partner', 'bonus_rank_1', 'bonus_rank_2', 'bonus_rank_3'] as $field) {
|
||||
$data[$field] = isset($data[$field]) && $data[$field] !== '' ? round((float) $data[$field], 2) : null;
|
||||
}
|
||||
$data['include_sales_points'] = filter_var($data['include_sales_points'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$incentive = Incentive::findOrFail($id);
|
||||
|
|
@ -102,8 +152,14 @@ class IncentiveController extends Controller
|
|||
'min_direct_partners' => 'required|integer|min:0',
|
||||
'min_customer_abos' => 'required|integer|min:0',
|
||||
'max_winners' => 'required|integer|min:1',
|
||||
'amount_per_partner' => 'nullable|numeric|min:0|max:999999',
|
||||
'bonus_rank_1' => 'nullable|numeric|min:0|max:999999',
|
||||
'bonus_rank_2' => 'nullable|numeric|min:0|max:999999',
|
||||
'bonus_rank_3' => 'nullable|numeric|min:0|max:999999',
|
||||
'include_sales_points' => 'nullable|boolean',
|
||||
'status' => 'required|integer|in:0,1,2',
|
||||
]);
|
||||
$data = $this->normalizePayoutFields($data);
|
||||
|
||||
$data = array_merge($data, $this->extractTranslations());
|
||||
|
||||
|
|
|
|||
|
|
@ -57,18 +57,24 @@ class HomeController extends Controller
|
|||
|
||||
$user = Auth::user();
|
||||
|
||||
$activeIncentive = null;
|
||||
$incentiveParticipant = null;
|
||||
// Mehrere Incentives koennen parallel aktiv sein (E1): je eine Dashboard-Karte.
|
||||
$activeIncentives = collect();
|
||||
$incentiveParticipants = collect();
|
||||
|
||||
if ($user->isActiveAccount()) {
|
||||
$activeIncentive = Incentive::active()->first();
|
||||
if ($activeIncentive) {
|
||||
$incentiveParticipant = IncentiveParticipant::where('incentive_id', $activeIncentive->id)
|
||||
$activeIncentives = Incentive::activeForNavigation();
|
||||
if ($activeIncentives->isNotEmpty()) {
|
||||
$incentiveParticipants = IncentiveParticipant::query()
|
||||
->whereIn('incentive_id', $activeIncentives->pluck('id'))
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
->get()
|
||||
->keyBy('incentive_id');
|
||||
}
|
||||
}
|
||||
|
||||
$activeIncentive = $activeIncentives->first();
|
||||
$incentiveParticipant = $activeIncentive ? $incentiveParticipants->get($activeIncentive->id) : null;
|
||||
|
||||
// Eigenes Abo des Beraters für die Einmalprodukte-Dashboard-Card (nur wenn
|
||||
// das Zeitfenster offen ist und das Feature für den User sichtbar ist).
|
||||
$oneTimeAbo = null;
|
||||
|
|
@ -96,6 +102,8 @@ class HomeController extends Controller
|
|||
'dashboardNews' => DashboardNews::getActiveNews(),
|
||||
'activeIncentive' => $activeIncentive,
|
||||
'incentiveParticipant' => $incentiveParticipant,
|
||||
'activeIncentives' => $activeIncentives,
|
||||
'incentiveParticipants' => $incentiveParticipants,
|
||||
'oneTimeAbo' => $oneTimeAbo,
|
||||
'monthlyStats' => $monthlyStats,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
|||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\UserAbo;
|
||||
use App\Services\Incentive\IncentivePayoutService;
|
||||
use App\Services\Incentive\IncentivePointsLogRepairService;
|
||||
use App\Services\Incentive\IncentiveTracker;
|
||||
use App\User;
|
||||
|
|
@ -68,6 +69,16 @@ class IncentiveController extends Controller
|
|||
$participateHasTrackableAbos = $this->userHasTrackableAbosForIncentive($user, $incentive);
|
||||
}
|
||||
|
||||
$payoutService = new IncentivePayoutService;
|
||||
$participantPayout = $incentive->hasCashPayout() && $participant
|
||||
? $payoutService->forParticipant($incentive, $participant)
|
||||
: null;
|
||||
$rankingPayouts = $incentive->hasCashPayout()
|
||||
? $ranking->getCollection()->mapWithKeys(fn (IncentiveParticipant $row) => [
|
||||
$row->id => $payoutService->forParticipant($incentive, $row),
|
||||
])
|
||||
: collect();
|
||||
|
||||
return view('user.incentive.show', [
|
||||
'incentive' => $incentive,
|
||||
'participant' => $participant,
|
||||
|
|
@ -75,6 +86,8 @@ class IncentiveController extends Controller
|
|||
'ranking' => $ranking,
|
||||
'participateHasTrackableAbos' => $participateHasTrackableAbos,
|
||||
'isVipView' => $user->isVIP(),
|
||||
'participantPayout' => $participantPayout,
|
||||
'rankingPayouts' => $rankingPayouts,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue