88 lines
3.3 KiB
PHP
88 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Organization;
|
|
use App\Models\User;
|
|
use Illuminate\Console\Command;
|
|
|
|
/**
|
|
* One-off: give incomplete_at a plausible historical date.
|
|
*
|
|
* profiles:mark-incomplete stamps "now" the first time it runs, so every
|
|
* profile that was already incomplete shares a single timestamp and the
|
|
* statistics chart shows one vertical step instead of a history.
|
|
*
|
|
* IMPORTANT: the database records no history of profile completeness — the
|
|
* fields the rule reads are mutable and carry no audit trail. Any backdated
|
|
* date is therefore an estimate, not a fact. Registration date is used as the
|
|
* best defensible approximation: a profile that is incomplete today and has
|
|
* never been filled in was, in all likelihood, incomplete from the start. It
|
|
* overstates history for the rarer case of a profile that was once complete
|
|
* and later emptied.
|
|
*
|
|
* Only touches rows stamped by the initial bulk run (same day), so it cannot
|
|
* overwrite a genuine timestamp recorded by a later nightly run.
|
|
*/
|
|
class BackdateIncompleteProfiles extends Command
|
|
{
|
|
protected $signature = 'profiles:backdate-incomplete
|
|
{--dry-run : Report what would change without writing}
|
|
{--on= : Only backdate rows stamped on this date (Y-m-d); defaults to today}';
|
|
|
|
protected $description = 'Estimate when each incomplete profile became incomplete, so the statistics show a history';
|
|
|
|
private const TYPES = [User::class, Organization::class];
|
|
|
|
public function handle(): int
|
|
{
|
|
$dryRun = (bool) $this->option('dry-run');
|
|
$on = $this->option('on')
|
|
? \Carbon\Carbon::parse($this->option('on'))->startOfDay()
|
|
: now()->startOfDay();
|
|
|
|
$this->warn('Backdated dates are estimates: profile completeness has no recorded history.');
|
|
$this->line('Only rows stamped on ' . $on->toDateString() . ' are touched.');
|
|
|
|
$changed = 0;
|
|
$skipped = 0;
|
|
|
|
foreach (self::TYPES as $type) {
|
|
$table = (new $type)->getTable();
|
|
|
|
if (!\Schema::hasColumn($table, 'incomplete_at')) {
|
|
continue;
|
|
}
|
|
|
|
$type::query()
|
|
->whereNotNull('incomplete_at')
|
|
->whereBetween('incomplete_at', [$on, $on->copy()->endOfDay()])
|
|
->chunkById(500, function ($profiles) use (&$changed, &$skipped, $dryRun) {
|
|
foreach ($profiles as $profile) {
|
|
$estimate = $profile->created_at;
|
|
|
|
// Nothing to move it to, or it would land in the
|
|
// future: leave the bulk stamp alone.
|
|
if (!$estimate || $estimate->gte($profile->incomplete_at)) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
if (!$dryRun) {
|
|
$profile->incomplete_at = $estimate;
|
|
$profile->saveQuietly();
|
|
}
|
|
|
|
$changed++;
|
|
}
|
|
});
|
|
}
|
|
|
|
$this->info($dryRun
|
|
? "Dry run: {$changed} profiles would be backdated, {$skipped} left as they are."
|
|
: "Backdated {$changed} profiles, left {$skipped} as they are.");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
}
|