Allow store deletion when only archived lists reference it
Archived lists are no longer part of anyone''s workflow, so they shouldn''t block a store from being deleted. The handler now blocks only on non-archived lists and purges archived ones (items cascade) in the same transaction. Also grooms BACKLOG.md to remove items already shipped (Family/multi- tenant epic, password reset, email invites, auth rate limiting, picked-up-vs-removed, per-store sections base feature, add-recipe-to- list, store delete confirm + duplicate-name 409).
This commit is contained in:
@@ -70,7 +70,7 @@ public class StoreEndpointsTests : AuthenticatedIntegrationTest
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Delete_blocks_when_store_has_lists()
|
||||
public async Task Delete_blocks_when_store_has_active_lists()
|
||||
{
|
||||
var store = await Data.CreateStoreAsync();
|
||||
await Data.CreateListAsync(b => b.ForStore(store).CreatedBy(User));
|
||||
@@ -81,6 +81,19 @@ public class StoreEndpointsTests : AuthenticatedIntegrationTest
|
||||
await Assert.That(await UseDbAsync(db => db.Stores.AnyAsync(s => s.Id == store.Id))).IsTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Delete_succeeds_when_store_only_has_archived_lists()
|
||||
{
|
||||
var store = await Data.CreateStoreAsync();
|
||||
await Data.CreateListAsync(b => b.ForStore(store).CreatedBy(User).Archived());
|
||||
|
||||
var response = await Client.DeleteAsync($"/api/stores/{store.Id}");
|
||||
|
||||
await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.NoContent);
|
||||
await Assert.That(await UseDbAsync(db => db.Stores.AnyAsync(s => s.Id == store.Id))).IsFalse();
|
||||
await Assert.That(await UseDbAsync(db => db.ShoppingLists.AnyAsync(l => l.StoreId == store.Id))).IsFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Create_returns_409_for_duplicate_name()
|
||||
{
|
||||
|
||||
@@ -71,8 +71,16 @@ public static class StoreEndpoints
|
||||
var store = await db.Stores.FirstOrDefaultAsync(s => s.Id == id && s.FamilyId == familyId);
|
||||
if (store is null) return Results.NotFound();
|
||||
|
||||
var hasLists = await db.ShoppingLists.AnyAsync(l => l.StoreId == id && l.FamilyId == familyId);
|
||||
if (hasLists) return Results.Conflict(new { error = "Store has shopping lists. Remove them first." });
|
||||
var hasActiveLists = await db.ShoppingLists
|
||||
.AnyAsync(l => l.StoreId == id && l.FamilyId == familyId && !l.IsArchived);
|
||||
if (hasActiveLists) return Results.Conflict(new { error = "Store has active shopping lists. Archive or remove them first." });
|
||||
|
||||
// Archived lists no longer belong to anyone's workflow — purge them (and their items, via cascade)
|
||||
// so the store can be deleted without leaving FK-orphaned history rows.
|
||||
var archivedLists = await db.ShoppingLists
|
||||
.Where(l => l.StoreId == id && l.FamilyId == familyId && l.IsArchived)
|
||||
.ToListAsync();
|
||||
if (archivedLists.Count > 0) db.ShoppingLists.RemoveRange(archivedLists);
|
||||
|
||||
db.Stores.Remove(store);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Reference in New Issue
Block a user