inconsistentlyMerged()->count(); } /** * The rows themselves, for a human to look at before deciding. * * @return \Illuminate\Database\Eloquent\Collection */ public static function rows() { return Ticket::query()->inconsistentlyMerged()->orderBy('id')->get(); } /** Has the operator asked for the deploy-time repair? */ public static function enabledOnDeploy(): bool { return filter_var(Setting::get(self::SETTING, '0'), FILTER_VALIDATE_BOOLEAN); } /** * Re-mark every inconsistent row as merged. Returns how many rows changed. * * IDEMPOTENT, and it is the scope that makes it so rather than a flag: the * second run matches nothing, because the first run's rows no longer satisfy * `status <> 'merged'`. So a migration that is re-run, a command run twice * and a deploy that repeats all answer 0 and write nothing. */ public static function repairAll(string $by): int { $tickets = Ticket::query()->inconsistentlyMerged()->orderBy('id')->get(); if ($tickets->isEmpty()) { Log::info("[ticketing] #938 merged-ticket repair ({$by}): nothing to repair."); return 0; } // Per model, not one builder update (#4781/#4807): `merged` makes a ticket // permanently read-only, and a bulk statement would freeze it with nothing // on the Changes log — while repair() below, the same change one ticket at // a time, is recorded. $tickets->each(fn (Ticket $ticket) => $ticket->forceFill(['status' => Ticket::STATUS_MERGED])->save()); $numbers = $tickets->pluck('number')->all(); $changed = $tickets->count(); // The numbers, not just the count. These rows are being made read-only, // and "which ones" is the question the office will ask afterwards. Log::info(sprintf( '[ticketing] #938 merged-ticket repair (%s): re-marked %d ticket(s) as merged — %s', $by, $changed, implode(', ', $numbers), )); return $changed; } /** * Re-mark ONE row. Returns false when it was not inconsistent to begin with, * so a double-clicked button and a stale page are both no-ops rather than a * second write. */ public static function repair(Ticket $ticket, string $by): bool { if (! $ticket->isInconsistentlyMerged()) { return false; } $ticket->forceFill(['status' => Ticket::STATUS_MERGED])->save(); Log::info(sprintf( '[ticketing] #938 merged-ticket repair (%s): re-marked %s as merged (merged into #%d).', $by, $ticket->number, $ticket->merged_into_id, )); return true; } }