10.April 2026
This commit is contained in:
parent
a00c42e770
commit
f58c709945
208 changed files with 19280 additions and 2914 deletions
169
app/Console/Commands/AboStoreChartSnapshots.php
Normal file
169
app/Console/Commands/AboStoreChartSnapshots.php
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue