Fix reorder: normalize SortOrder after move, remove AsNoTracking from write path

The shift-in-place approach broke when SortOrder values had gaps from soft
deletes. Switch to remove/insert then assign SortOrder = index, which is
correct regardless of initial values. Also fix OutgosController Reorder
which had AsNoTracking applied from the M-3 change, silently dropping saves.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Spencer Twaddle
2026-05-06 22:24:30 -05:00
parent b82b3939ce
commit 4fa35dadc3
2 changed files with 9 additions and 11 deletions
@@ -105,11 +105,10 @@ public class IncomesController(AppDbContext db, BudgetAuthorizationService authz
var oldIdx = incomes.IndexOf(item);
var newIdx = Math.Clamp(req.NewIndex, 0, incomes.Count - 1);
if (oldIdx == newIdx) return NoContent();
if (newIdx < oldIdx)
for (int i = newIdx; i < oldIdx; i++) incomes[i].SortOrder++;
else
for (int i = oldIdx + 1; i <= newIdx; i++) incomes[i].SortOrder--;
item.SortOrder = newIdx;
incomes.RemoveAt(oldIdx);
incomes.Insert(newIdx, item);
for (int i = 0; i < incomes.Count; i++)
incomes[i].SortOrder = i;
await db.SaveChangesAsync();
return NoContent();
}
@@ -116,17 +116,16 @@ public class OutgosController(AppDbContext db, BudgetAuthorizationService authz)
{
if (TryGetUserId(out var userId) is { } err) return err;
if (!await authz.CanWriteAsync(budgetId, userId)) return Forbid();
var outgos = await db.Outgos.AsNoTracking().Where(o => o.BudgetId == budgetId).OrderBy(o => o.SortOrder).ToListAsync();
var outgos = await db.Outgos.Where(o => o.BudgetId == budgetId).OrderBy(o => o.SortOrder).ToListAsync();
var item = outgos.FirstOrDefault(o => o.Id == req.Id);
if (item is null) return NotFound();
var oldIdx = outgos.IndexOf(item);
var newIdx = Math.Clamp(req.NewIndex, 0, outgos.Count - 1);
if (oldIdx == newIdx) return NoContent();
if (newIdx < oldIdx)
for (int i = newIdx; i < oldIdx; i++) outgos[i].SortOrder++;
else
for (int i = oldIdx + 1; i <= newIdx; i++) outgos[i].SortOrder--;
item.SortOrder = newIdx;
outgos.RemoveAt(oldIdx);
outgos.Insert(newIdx, item);
for (int i = 0; i < outgos.Count; i++)
outgos[i].SortOrder = i;
await db.SaveChangesAsync();
return NoContent();
}