Deleting a merge target un-merges what was folded into it (#1567)
`tickets.merged_into_id` is `nullOnDelete()` and `destroy()` was a one-liner, so deleting the surviving ticket of a merge blanked the pointer on every ticket merged into it and left `status = 'merged'` behind. The row that came out was read-only for good (#920 guards on the status), carried a banner naming a ticket that no longer existed, reported `merged_inconsistent = false` — the flag that exists to catch exactly this — and dropped out of the Merged view, which was scoped by the pointer, leaving it visible only under Resolved. `StrandedMerge::handleTargetDeletion()` runs inside destroy()'s transaction, before the row goes: sources are RE-POINTED at the doomed ticket's own merge target when it has one (the messages really did travel that far up the chain), otherwise UN-MERGED to `closed` — out of the work queues, as they were, but editable, reopenable and answerable again — with a system note naming what was deleted and why. #938's dangling pointers are simply cleared: those rows are somebody's open work. The flash names every ticket that changed, and the delete confirm names them before the click instead of the generic "cannot be undone". The shape prod already holds gets a name: `scopeStrandedMerge()`, reported as `merged_inconsistent` (now the union of both broken shapes) plus `merged_stranded` so a page can tell which fix to offer, listed by the Merged view via `scopeEverMerged()`, and released one at a time by `unmerge`. #938's repair scope is deliberately untouched — it writes `status = 'merged'`, which a stranded row already has, and matching it would break its idempotency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,8 @@ Route::middleware(['web', 'auth', 'app.access:ticketing'])
|
||||
Route::post('/{ticket}/split', [TicketController::class, 'split']) ->name('split');
|
||||
// #938 — the one-click fix for a row the merge bug left inconsistent.
|
||||
Route::post('/{ticket}/mark-merged', [TicketController::class, 'markMerged']) ->name('mark-merged');
|
||||
// #1567 — the one-click release for a row whose merge target was deleted.
|
||||
Route::post('/{ticket}/unmerge', [TicketController::class, 'unmerge']) ->name('unmerge');
|
||||
Route::post('/{ticket}/participants', [TicketController::class, 'addParticipant']) ->name('participants.store');
|
||||
Route::delete('/{ticket}/participants/{userId}', [TicketController::class, 'removeParticipant'])->name('participants.destroy');
|
||||
Route::get('/{ticket}/users/search', [TicketController::class, 'searchUsers']) ->name('users.search');
|
||||
|
||||
Reference in New Issue
Block a user