205 lines
8.4 KiB
PHP
205 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Incentive;
|
|
use App\Models\IncentiveParticipant;
|
|
use App\Services\Incentive\IncentivePointsLogRepairService;
|
|
use App\Services\Incentive\IncentiveTracker;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Batch-Neuberechnung fuer Incentives.
|
|
*
|
|
* Abo-Wertung (nicht hier codiert, sondern in {@see IncentiveParticipant::rebuildFromSourceTables}
|
|
* und {@see IncentivePointsLogRepairService::syncMissingTrackingAbos}):
|
|
*
|
|
* - Eigenabo (me): zaehlt auch wenn es vor dem Qualifikationszeitraum abgeschlossen wurde;
|
|
* Einmalpunkte wirken dann ab Qualifikationsbeginn (aktiviert_at/Log-Monat auf Start des Zeitraums).
|
|
* - Kundenabo (ot): nur wenn im Qualifikationszeitraum neu abgeschlossen (created_at im Zeitraum).
|
|
*
|
|
* Zu Beginn werden fuer alle Berater (User mit m_level) fehlende Teilnehmerzeilen ohne
|
|
* accepted_terms angelegt ({@see IncentiveParticipant::ensureConsultantsForIncentive}), damit
|
|
* Punkte ohne Checkbox mitlaufen; die Rangliste blendet Namen erst nach Zustimmung ein.
|
|
*/
|
|
class IncentiveCalculate extends Command
|
|
{
|
|
protected $signature = 'incentive:calculate
|
|
{incentive_id? : ID des Incentives (leer = alle aktiven)}
|
|
{--force : Tracking-Tabellen + Log loeschen und komplett neu aufbauen}
|
|
{--skip-repair : Kein Nachziehen von Trackings/FKs/SV-Logs (nur Summen aus bestehendem Log)}
|
|
{--verbose-details : Zeigt Details pro Teilnehmer}';
|
|
|
|
protected $description = 'Incentive-Punkte: fehlende Partner-/Abo-Trackings, FK-Reparatur, fehlende SV-Logs, Summen/Ranking; --force = kompletter Neuaufbau aus Quelldaten';
|
|
|
|
public function handle(IncentivePointsLogRepairService $repairService): int
|
|
{
|
|
if ($id = $this->argument('incentive_id')) {
|
|
$incentive = Incentive::find($id);
|
|
if (! $incentive) {
|
|
$this->error("Incentive #{$id} nicht gefunden.");
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
return $this->processIncentive($incentive, $repairService);
|
|
}
|
|
|
|
$incentives = Incentive::active()->get();
|
|
if ($incentives->isEmpty()) {
|
|
$this->info('Keine aktiven Incentives gefunden.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$exitCode = self::SUCCESS;
|
|
foreach ($incentives as $incentive) {
|
|
if ($this->processIncentive($incentive, $repairService) !== self::SUCCESS) {
|
|
$exitCode = self::FAILURE;
|
|
}
|
|
}
|
|
|
|
return $exitCode;
|
|
}
|
|
|
|
private function processIncentive(Incentive $incentive, IncentivePointsLogRepairService $repairService): int
|
|
{
|
|
$force = $this->option('force');
|
|
$skipRepair = $this->option('skip-repair');
|
|
$verbose = $this->option('verbose-details');
|
|
|
|
$this->info("=== {$incentive->name} (ID: {$incentive->id}) ===");
|
|
$this->info(" Zeitraum: {$incentive->qualification_start->format('d.m.Y')} - {$incentive->qualification_end->format('d.m.Y')}");
|
|
if ($force) {
|
|
$this->info(' Modus: FORCE (Tracking + Log aus Quelldaten neu aufbauen)');
|
|
} elseif ($skipRepair) {
|
|
$this->info(' Modus: Nur Neuberechnung (Summen/Ranking aus bestehendem Log)');
|
|
} else {
|
|
$this->info(' Modus: Tracking nachziehen + FK-Reparatur + SV-Logs + Neuberechnung');
|
|
}
|
|
|
|
$stubAdded = IncentiveParticipant::ensureConsultantsForIncentive($incentive);
|
|
if ($stubAdded > 0) {
|
|
$this->info(" Berater-Teilnehmer neu angelegt (ohne Zustimmung): {$stubAdded}");
|
|
}
|
|
|
|
$participants = $incentive->participants()->with('user', 'user.account')->get();
|
|
$this->info(" Teilnehmer: {$participants->count()}");
|
|
$this->newLine();
|
|
|
|
$stats = [
|
|
'processed' => 0,
|
|
'errors' => 0,
|
|
'with_points' => 0,
|
|
'with_partners' => 0,
|
|
'with_abos' => 0,
|
|
'tracking_partner_added' => 0,
|
|
'tracking_abo_added' => 0,
|
|
'repair_partner_fk' => 0,
|
|
'repair_abo_fk' => 0,
|
|
'repair_onetime_partner_fk' => 0,
|
|
'repair_onetime_abo_fk' => 0,
|
|
'sv_logs_added' => 0,
|
|
];
|
|
$errors = [];
|
|
|
|
$bar = $this->output->createProgressBar($participants->count());
|
|
$bar->start();
|
|
|
|
foreach ($participants as $participant) {
|
|
try {
|
|
if (! $participant->user) {
|
|
$bar->advance();
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($force) {
|
|
$participant->rebuildFromSourceTables()->save();
|
|
} else {
|
|
if (! $skipRepair) {
|
|
$stats['tracking_partner_added'] += $repairService->syncMissingTrackingPartners($participant);
|
|
$stats['tracking_abo_added'] += $repairService->syncMissingTrackingAbos($participant);
|
|
$r = $repairService->repairForeignKeys($participant);
|
|
$stats['repair_partner_fk'] += $r['partner_fk'];
|
|
$stats['repair_abo_fk'] += $r['abo_fk'];
|
|
$stats['repair_onetime_partner_fk'] += $r['onetime_partner_fk'];
|
|
$stats['repair_onetime_abo_fk'] += $r['onetime_abo_fk'];
|
|
$stats['sv_logs_added'] += $repairService->syncMissingSalesVolumeLogs($participant);
|
|
}
|
|
$participant->recalculateFromTrackingTables()->save();
|
|
}
|
|
|
|
$stats['processed']++;
|
|
|
|
if ($participant->total_points > 0) {
|
|
$stats['with_points']++;
|
|
}
|
|
if ($participant->qualified_partners > 0) {
|
|
$stats['with_partners']++;
|
|
}
|
|
if ($participant->qualified_abos > 0) {
|
|
$stats['with_abos']++;
|
|
}
|
|
|
|
if ($verbose && $participant->total_points > 0) {
|
|
$name = $participant->user->account
|
|
? $participant->user->account->first_name.' '.$participant->user->account->last_name
|
|
: ($participant->user->email ?? 'User #'.$participant->user_id);
|
|
$bar->clear();
|
|
$this->line(" {$name}: {$participant->total_points} Pkt, {$participant->qualified_partners} Partner, {$participant->qualified_abos} Abos");
|
|
$bar->display();
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$stats['errors']++;
|
|
$errors[] = "Participant #{$participant->id} (User #{$participant->user_id}): {$e->getMessage()}";
|
|
Log::error('IncentiveCalculation error for participant '.$participant->id.': '.$e->getMessage());
|
|
}
|
|
|
|
$bar->advance();
|
|
}
|
|
|
|
$bar->finish();
|
|
$this->newLine(2);
|
|
|
|
IncentiveTracker::updateRanking($incentive);
|
|
$ranked = IncentiveParticipant::where('incentive_id', $incentive->id)
|
|
->whereNotNull('rank')
|
|
->count();
|
|
|
|
$tableRows = [
|
|
['Verarbeitet', (string) $stats['processed']],
|
|
['Fehler', (string) $stats['errors']],
|
|
['Mit Punkten', (string) $stats['with_points']],
|
|
['Mit Partnern', (string) $stats['with_partners']],
|
|
['Mit Abos', (string) $stats['with_abos']],
|
|
['Im Ranking', (string) $ranked],
|
|
];
|
|
|
|
if (! $force) {
|
|
$tableRows[] = ['Neupartner-Trackings nachgezogen', (string) $stats['tracking_partner_added']];
|
|
$tableRows[] = ['Neuabo-Trackings nachgezogen', (string) $stats['tracking_abo_added']];
|
|
$tableRows[] = ['FK Partner (akkum.) repariert', (string) $stats['repair_partner_fk']];
|
|
$tableRows[] = ['FK Abo (akkum.) repariert', (string) $stats['repair_abo_fk']];
|
|
$tableRows[] = ['FK Partner (Einmal) repariert', (string) $stats['repair_onetime_partner_fk']];
|
|
$tableRows[] = ['FK Abo (Einmal) repariert', (string) $stats['repair_onetime_abo_fk']];
|
|
$tableRows[] = ['Neue SV-Log-Eintraege', (string) $stats['sv_logs_added']];
|
|
}
|
|
|
|
$this->table(['Metrik', 'Wert'], $tableRows);
|
|
|
|
if (! empty($errors)) {
|
|
$this->error('Fehler:');
|
|
foreach ($errors as $err) {
|
|
$this->line(" - {$err}");
|
|
}
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->info('Fertig.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|