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
|
|
@ -31,6 +31,11 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
* @property int $min_direct_partners
|
||||
* @property int $min_customer_abos
|
||||
* @property int $max_winners
|
||||
* @property float|null $amount_per_partner
|
||||
* @property float|null $bonus_rank_1
|
||||
* @property float|null $bonus_rank_2
|
||||
* @property float|null $bonus_rank_3
|
||||
* @property bool $include_sales_points
|
||||
* @property int $status
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
|
|
@ -59,6 +64,11 @@ class Incentive extends Model
|
|||
'min_direct_partners' => 'int',
|
||||
'min_customer_abos' => 'int',
|
||||
'max_winners' => 'int',
|
||||
'amount_per_partner' => 'float',
|
||||
'bonus_rank_1' => 'float',
|
||||
'bonus_rank_2' => 'float',
|
||||
'bonus_rank_3' => 'float',
|
||||
'include_sales_points' => 'bool',
|
||||
'status' => 'int',
|
||||
'qualification_start' => 'date',
|
||||
'qualification_end' => 'date',
|
||||
|
|
@ -83,9 +93,19 @@ class Incentive extends Model
|
|||
'min_direct_partners',
|
||||
'min_customer_abos',
|
||||
'max_winners',
|
||||
'amount_per_partner',
|
||||
'bonus_rank_1',
|
||||
'bonus_rank_2',
|
||||
'bonus_rank_3',
|
||||
'include_sales_points',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Anzahl der Plaetze, fuer die ein Rang-Bonus hinterlegt werden kann.
|
||||
*/
|
||||
public const RANK_BONUS_SLOTS = 3;
|
||||
|
||||
public static $statusTypes = [
|
||||
0 => 'draft',
|
||||
1 => 'active',
|
||||
|
|
@ -121,6 +141,14 @@ class Incentive extends Model
|
|||
return $query->where('status', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Incentives, bei denen Umsatzpunkte der Neupartner/Neuabos mitzaehlen.
|
||||
*/
|
||||
public function scopeIncludingSalesPoints($query)
|
||||
{
|
||||
return $query->where('include_sales_points', true);
|
||||
}
|
||||
|
||||
public function scopeInQualificationPeriod($query, ?Carbon $date = null)
|
||||
{
|
||||
$date = $date ?: Carbon::now();
|
||||
|
|
@ -149,6 +177,20 @@ class Incentive extends Model
|
|||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Aktive Incentives fuer Sidenav und Dashboard: mehrere koennen parallel laufen
|
||||
* (z. B. Montenegro bis Oktober, Recruiting-Challenge ab Oktober), aelteste zuerst.
|
||||
*
|
||||
* @return Collection<int, Incentive>
|
||||
*/
|
||||
public static function activeForNavigation(): Collection
|
||||
{
|
||||
return static::active()
|
||||
->orderBy('qualification_start')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->status === 1;
|
||||
|
|
@ -198,6 +240,76 @@ class Incentive extends Model
|
|||
return self::$statusColors[$this->status] ?? 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird eine Geldpraemie je Neupartner ausgezahlt (Recruiting-Challenge)?
|
||||
*/
|
||||
public function hasCashPayout(): bool
|
||||
{
|
||||
return (float) $this->amount_per_partner > 0 || $this->rankBonuses() !== [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rang-Boni je Platz (nur gesetzte Betraege > 0, Platz => Betrag).
|
||||
*
|
||||
* @return array<int, float>
|
||||
*/
|
||||
public function rankBonuses(): array
|
||||
{
|
||||
$bonuses = [];
|
||||
for ($rank = 1; $rank <= self::RANK_BONUS_SLOTS; $rank++) {
|
||||
$amount = (float) $this->{'bonus_rank_'.$rank};
|
||||
if ($amount > 0) {
|
||||
$bonuses[$rank] = $amount;
|
||||
}
|
||||
}
|
||||
|
||||
return $bonuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rang-Bonus fuer einen Platz, 0 wenn keiner hinterlegt oder der Platz ausserhalb der Gewinnerplaetze liegt.
|
||||
*/
|
||||
public function rankBonus(?int $rank): float
|
||||
{
|
||||
if ($rank === null || $rank < 1 || $rank > $this->max_winners) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return $this->rankBonuses()[$rank] ?? 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zaehlen Umsatzpunkte der Neupartner/Neuabos mit? Aus = Wertung nur nach Einmalpunkten.
|
||||
*/
|
||||
public function includesSalesPoints(): bool
|
||||
{
|
||||
return $this->include_sales_points === null || (bool) $this->include_sales_points;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt es eine Mindestqualifikation (direkte Partner oder Kundenabos)?
|
||||
*/
|
||||
public function hasMinimumRequirements(): bool
|
||||
{
|
||||
return $this->min_direct_partners > 0 || $this->min_customer_abos > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielen Abos in diesem Incentive eine Rolle (Punkte oder Mindestanzahl)?
|
||||
*/
|
||||
public function usesAbos(): bool
|
||||
{
|
||||
return $this->points_abo_onetime > 0 || $this->min_customer_abos > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rangliste nach Anzahl Neupartner: Einmalpunkte je Partner, keine Abo- und Umsatzpunkte.
|
||||
*/
|
||||
public function ranksByPartnerCount(): bool
|
||||
{
|
||||
return ! $this->includesSalesPoints() && ! $this->usesAbos() && $this->points_partner_onetime > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific translation for a field and locale.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -365,6 +365,11 @@ class IncentiveParticipant extends Model
|
|||
}
|
||||
|
||||
// C. Akkumulierte Punkte NUR von Neupartnern und Neuabos
|
||||
// (entfaellt bei Incentives ohne Umsatzpunkte, z. B. Recruiting-Challenge)
|
||||
if (! $incentive->includesSalesPoints()) {
|
||||
return $this->recalculateFromTrackingTables();
|
||||
}
|
||||
|
||||
$calculation_months = $incentive->getCalculationMonths();
|
||||
$new_partner_user_ids = $this->newPartners()->pluck('user_id')->toArray();
|
||||
$abo_shopping_user_ids = UserAbo::whereIn('id', $this->newAbos()->pluck('user_abo_id'))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue