Snapshot of main at 9f39a6e1e (2026-11-02)

This commit is contained in:
Ronald Huynen committed 2026-11-02 10:41:47 +01:00
commit 6ccf1792d7
2580 files changed
+1040202

No files matched your search

@@ -0,0 +1,87 @@
<?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;
}
}