Kundenentscheid 25.09.2026 zu den Regeln 4 bis 7: - Firmenkonto (mivita.company_user_id) steht in keiner Rangliste und bekommt keine Praemie; getrackt wird weiter, damit ein ueber den Firmenlink registrierter Partner nach dem Umhaengen mit umzieht - Bedingungen Punkt 4 (de/en/es/fr): auch Kunden, die Berater werden, zaehlen; massgeblich ist der aktuell hinterlegte Sponsor - Test: Anmeldung ueber den Firmenlink, Umhaengen, Wertung beim neuen Sponsor - Doku: Regeln 4 bis 7 entschieden Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
142 lines
5.6 KiB
PHP
142 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Incentive;
|
|
|
|
use App\Models\Incentive;
|
|
use App\Models\IncentiveParticipant;
|
|
use Illuminate\Support\Collection;
|
|
|
|
/**
|
|
* Geldpraemie eines Incentives (Recruiting-Challenge, Entwicklungsplan E1).
|
|
*
|
|
* Praemie = Anzahl Neupartner mit Starterpaket x Betrag je Neupartner, dazu der
|
|
* Rang-Bonus, wenn der Teilnehmer qualifiziert ist und innerhalb der Gewinnerplaetze
|
|
* liegt. Grundlage ist der gespeicherte Stand des Teilnehmers (qualified_partners, rank),
|
|
* also derselbe Stand wie in der Rangliste.
|
|
*
|
|
* Gerechnet wird fuer alle Teilnehmer; ausgezahlt wird nur an Teilnehmer mit bestaetigter
|
|
* Teilnahme (Kundenentscheid 25.09.2026). Unbestaetigte haben keinen Rang, also keinen Bonus.
|
|
*/
|
|
class IncentivePayoutService
|
|
{
|
|
/**
|
|
* @return array{partners: int, per_partner: float, partner_amount: float, rank_bonus: float, total: float, confirmed: bool}
|
|
*/
|
|
public function forParticipant(Incentive $incentive, IncentiveParticipant $participant): array
|
|
{
|
|
$partners = (int) $participant->qualified_partners;
|
|
$perPartner = (float) $incentive->amount_per_partner;
|
|
$partnerAmount = round($partners * $perPartner, 2);
|
|
$rankBonus = $participant->is_qualified ? $incentive->rankBonus($participant->rank) : 0.0;
|
|
|
|
return [
|
|
'partners' => $partners,
|
|
'per_partner' => $perPartner,
|
|
'partner_amount' => $partnerAmount,
|
|
'rank_bonus' => $rankBonus,
|
|
'total' => round($partnerAmount + $rankBonus, 2),
|
|
'confirmed' => $participant->accepted_terms_at !== null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Auszahlungszeilen: Teilnehmer mit bestaetigter Teilnahme und Praemie > 0, nach Rang.
|
|
*
|
|
* @return Collection<int, array{participant: IncentiveParticipant, payout: array{partners: int, per_partner: float, partner_amount: float, rank_bonus: float, total: float, confirmed: bool}}>
|
|
*/
|
|
public function rows(Incentive $incentive): Collection
|
|
{
|
|
return IncentiveParticipant::query()
|
|
->where('incentive_id', $incentive->id)
|
|
->whereNotIn('user_id', IncentiveParticipant::excludedUserIds())
|
|
->with('user', 'user.account')
|
|
->orderByRankNullsLast()
|
|
->orderByDesc('qualified_partners')
|
|
->get()
|
|
->map(fn (IncentiveParticipant $participant) => [
|
|
'participant' => $participant,
|
|
'payout' => $this->forParticipant($incentive, $participant),
|
|
])
|
|
->filter(fn (array $row) => $row['payout']['confirmed'] && $row['payout']['total'] > 0)
|
|
->values();
|
|
}
|
|
|
|
/**
|
|
* Summen ueber alle Auszahlungszeilen.
|
|
*
|
|
* @param Collection<int, array{participant: IncentiveParticipant, payout: array{partners: int, per_partner: float, partner_amount: float, rank_bonus: float, total: float, confirmed: bool}}> $rows
|
|
* @return array{recipients: int, partners: int, partner_amount: float, rank_bonus: float, total: float}
|
|
*/
|
|
public function totals(Collection $rows): array
|
|
{
|
|
return [
|
|
'recipients' => $rows->count(),
|
|
'partners' => (int) $rows->sum(fn (array $row) => $row['payout']['partners']),
|
|
'partner_amount' => round((float) $rows->sum(fn (array $row) => $row['payout']['partner_amount']), 2),
|
|
'rank_bonus' => round((float) $rows->sum(fn (array $row) => $row['payout']['rank_bonus']), 2),
|
|
'total' => round((float) $rows->sum(fn (array $row) => $row['payout']['total']), 2),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Spaltenueberschriften des XLSX-Exports.
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
public function exportHeaders(): array
|
|
{
|
|
return [
|
|
__('incentive.rank'),
|
|
__('incentive.export_user_id'),
|
|
__('incentive.consultant'),
|
|
'E-Mail',
|
|
__('incentive.admin_terms_accepted'),
|
|
__('incentive.partners'),
|
|
__('incentive.amount_per_partner'),
|
|
__('incentive.payout_partner_amount'),
|
|
__('incentive.payout_rank_bonus'),
|
|
__('incentive.payout_total'),
|
|
__('incentive.total_points'),
|
|
__('incentive.qualified'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Zeilen des XLSX-Exports (Betraege als Zahlen, damit Excel summieren kann).
|
|
*
|
|
* @return list<array<int, string|int|float>>
|
|
*/
|
|
public function exportRows(Incentive $incentive): array
|
|
{
|
|
return $this->rows($incentive)->map(function (array $row) {
|
|
$participant = $row['participant'];
|
|
$payout = $row['payout'];
|
|
$user = $participant->user;
|
|
$name = $user?->account
|
|
? trim($user->account->first_name.' '.$user->account->last_name)
|
|
: ($user?->getFullName() ?: '');
|
|
|
|
return [
|
|
$participant->rank ?? '',
|
|
(int) $participant->user_id,
|
|
$name,
|
|
(string) ($user?->email ?? ''),
|
|
$participant->accepted_terms_at
|
|
? $participant->accepted_terms_at->format('d.m.Y H:i')
|
|
: __('incentive.admin_terms_pending'),
|
|
$payout['partners'],
|
|
$payout['per_partner'],
|
|
$payout['partner_amount'],
|
|
$payout['rank_bonus'],
|
|
$payout['total'],
|
|
(int) $participant->total_points,
|
|
$participant->is_qualified ? __('incentive.yes') : __('incentive.no'),
|
|
];
|
|
})->all();
|
|
}
|
|
|
|
public static function formatAmount(float $amount): string
|
|
{
|
|
return number_format($amount, 2, ',', '.').' €';
|
|
}
|
|
}
|