169 lines
5.7 KiB
PHP
169 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\AboChartSnapshot;
|
|
use App\Models\UserAbo;
|
|
use App\Services\AboHelper;
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Console\Command;
|
|
|
|
class AboStoreChartSnapshots extends Command
|
|
{
|
|
protected $signature = 'abo:store-chart-snapshots
|
|
{--user= : Nur einen bestimmten User berechnen (user_id)}
|
|
{--force : Bereits vorhandene Snapshots überschreiben}';
|
|
|
|
protected $description = 'Speichert monatliche Abo-Zählungen aller vergangenen Monate in der Datenbank (einmalig je Monat)';
|
|
|
|
private const SCOPES = ['ot', 'team_abos', 'team_cust_abos'];
|
|
|
|
private const START_YEAR = 2026;
|
|
|
|
public function handle(): int
|
|
{
|
|
$now = Carbon::now();
|
|
$force = (bool) $this->option('force');
|
|
|
|
// Monate die eingefroren werden sollen: von START_YEAR/01 bis letzten Monat
|
|
$months = $this->getPastMonths($now);
|
|
if (empty($months)) {
|
|
$this->info('Keine vergangenen Monate zum Speichern.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
// User ermitteln
|
|
$userQuery = User::whereNotNull('m_level')
|
|
->whereNotNull('payment_account')
|
|
->where('admin', '<', 4)
|
|
->whereNull('deleted_at');
|
|
|
|
if ($userId = $this->option('user')) {
|
|
$userQuery->where('id', $userId);
|
|
}
|
|
|
|
$users = $userQuery->select('id')->get();
|
|
$total = $users->count();
|
|
$this->info("Berechne Snapshots für {$total} User, ".count($months).' Monate, '.count(self::SCOPES).' Scopes...');
|
|
|
|
$bar = $this->output->createProgressBar($total);
|
|
$bar->start();
|
|
|
|
$inserted = 0;
|
|
$skipped = 0;
|
|
|
|
foreach ($users as $user) {
|
|
// Bereits vorhandene Snapshots für diesen User laden (zum Überspringen)
|
|
$existing = AboChartSnapshot::where('user_id', $user->id)
|
|
->get()
|
|
->keyBy(fn ($s) => "{$s->scope}_{$s->year}_{$s->month}");
|
|
|
|
$teamUserIds = AboHelper::getTeamUserIds($user->id);
|
|
$rows = [];
|
|
|
|
foreach ($months as [$year, $month]) {
|
|
$startOfMonth = Carbon::create($year, $month, 1)->startOfMonth();
|
|
$endOfMonth = Carbon::create($year, $month, 1)->endOfMonth();
|
|
|
|
foreach (self::SCOPES as $scope) {
|
|
$key = "{$scope}_{$year}_{$month}";
|
|
|
|
if (! $force && $existing->has($key)) {
|
|
$skipped++;
|
|
|
|
continue;
|
|
}
|
|
|
|
$count = $this->calculateCount($scope, $user->id, $teamUserIds, $startOfMonth, $endOfMonth);
|
|
$rows[] = [
|
|
'user_id' => $user->id,
|
|
'scope' => $scope,
|
|
'year' => $year,
|
|
'month' => $month,
|
|
'count' => $count,
|
|
'calculated_at' => $now,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
$inserted++;
|
|
}
|
|
}
|
|
|
|
if (! empty($rows)) {
|
|
if ($force) {
|
|
foreach ($rows as $row) {
|
|
AboChartSnapshot::updateOrInsert(
|
|
['user_id' => $row['user_id'], 'scope' => $row['scope'], 'year' => $row['year'], 'month' => $row['month']],
|
|
$row
|
|
);
|
|
}
|
|
} else {
|
|
AboChartSnapshot::insertOrIgnore($rows);
|
|
}
|
|
}
|
|
|
|
$bar->advance();
|
|
gc_collect_cycles();
|
|
}
|
|
|
|
$bar->finish();
|
|
$this->newLine();
|
|
$this->info("Fertig. Gespeichert: {$inserted}, Übersprungen (bereits vorhanden): {$skipped}");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Berechnet die Abo-Anzahl für einen Scope/User/Monat anhand der tatsächlichen Daten zum Zeitpunkt der Berechnung.
|
|
*
|
|
* @param int[] $teamUserIds
|
|
*/
|
|
private function calculateCount(string $scope, int $userId, array $teamUserIds, Carbon $startOfMonth, Carbon $endOfMonth): int
|
|
{
|
|
$terminalStatuses = [4, 5];
|
|
|
|
$query = match ($scope) {
|
|
'ot' => UserAbo::where('member_id', $userId)
|
|
->where('is_for', 'ot')
|
|
->where('status', '>', 1),
|
|
'team_abos' => UserAbo::whereIn('user_id', $teamUserIds)
|
|
->where('is_for', 'me')
|
|
->where('status', '>', 1),
|
|
'team_cust_abos' => UserAbo::whereIn('member_id', $teamUserIds)
|
|
->where('is_for', 'ot')
|
|
->where('status', '>', 1),
|
|
};
|
|
|
|
return $query
|
|
->whereDate('start_date', '<=', $endOfMonth)
|
|
->where(function ($q) use ($startOfMonth, $terminalStatuses) {
|
|
$q->whereDate('cancel_date', '>=', $startOfMonth)
|
|
->orWhere(function ($q2) use ($terminalStatuses) {
|
|
$q2->whereNull('cancel_date')
|
|
->whereNotIn('status', $terminalStatuses);
|
|
});
|
|
})
|
|
->count();
|
|
}
|
|
|
|
/**
|
|
* Alle abgeschlossenen Monate von START_YEAR/01 bis letzten Monat.
|
|
*
|
|
* @return array<array{int, int}>
|
|
*/
|
|
private function getPastMonths(Carbon $now): array
|
|
{
|
|
$months = [];
|
|
$cursor = Carbon::create(self::START_YEAR, 1, 1);
|
|
$lastMonth = $now->copy()->subMonth()->endOfMonth();
|
|
|
|
while ($cursor->lte($lastMonth)) {
|
|
$months[] = [(int) $cursor->year, (int) $cursor->month];
|
|
$cursor->addMonth();
|
|
}
|
|
|
|
return $months;
|
|
}
|
|
}
|