feat(sammler): mehrere sammler mit eigener sammlung und wunschliste

Der Katalog bleibt gemeinsam - ein Set existiert einmal, unabhaengig
davon, wer es hat. Besitz und Wunschlisten gehoeren dagegen je einem
Sammler. Damit beantwortet "hat er das schon?" jetzt die Frage "hat *er*
das schon?": ein Set, das nur die Schwester besitzt, faellt fuer den
Bruder wieder unter "fehlt noch" und darf auf seine Wunschliste.

Wichtig fuer den Alltag: Solange es nur einen Sammler gibt, taucht die
Unterscheidung nirgends in der Oberflaeche auf. Kein Umschalter, keine
Abfrage beim Hinzufuegen, keine Badges - die App bleibt exakt so
einfach wie vorher. Erst der zweite Sammler schaltet all das frei.

Die Besitzlogik liegt weiterhin an genau einer Stelle
(src/lib/ownership.ts) und hat nur eine Dimension dazubekommen. Das
automatische "erfuellt" auf Wunschlisten fragt jetzt ebenfalls je Liste:
ein Wunsch auf Max' Liste ist erfuellt, wenn Max das Set bekommt - nicht
wenn Mia es hat. Listen ohne Sammler verhalten sich wie bisher.

Die Migration legt einen Sammler "Sammlung" an und ordnet ihm alles
Vorhandene zu; umbenennen laesst er sich unter Verwaltung -> Sammler.
Der Seed uebernimmt ihn statt einen dritten anzulegen.

Refs Phase 3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
unknown
2026-09-07 09:00:35 +02:00
co-authored by Claude Opus 5
parent 7265c03548
commit c23b754515
35 changed files with 3104 additions and 66 deletions
+9 -7
View File
@@ -14,6 +14,8 @@ export type CatalogFilters = {
ownership?: OwnershipFilter;
/** Hides sets that are already on this wishlist (for "aus fehlenden Sets hinzufügen"). */
excludeWishlistId?: string;
/** Scopes "owned" and "on a wishlist" to one collector. */
collectorId?: string;
page?: number;
};
@@ -52,7 +54,7 @@ function buildWhere(filters: CatalogFilters): SQL | undefined {
if (filters.themeId) conditions.push(eq(set.themeId, filters.themeId));
if (filters.year !== undefined) conditions.push(eq(set.year, filters.year));
const owned = ownedCopiesSql(set.id);
const owned = ownedCopiesSql(set.id, filters.collectorId);
switch (filters.ownership) {
case 'sammlung':
conditions.push(sql`${owned} > 0`);
@@ -61,7 +63,7 @@ function buildWhere(filters: CatalogFilters): SQL | undefined {
conditions.push(sql`${owned} = 0`);
break;
case 'wunschliste':
conditions.push(sql`${wishlistCountSql(set.id)} > 0`);
conditions.push(sql`${wishlistCountSql(set.id, filters.collectorId)} > 0`);
break;
default:
break;
@@ -95,8 +97,8 @@ export function listSets(filters: CatalogFilters): { rows: CatalogRow[]; total:
retailPriceCents: set.retailPriceCents,
manufacturerName: manufacturer.name,
themeName: theme.name,
ownedCopies: ownedCopiesSql(set.id).mapWith(Number),
wishlistCount: wishlistCountSql(set.id).mapWith(Number),
ownedCopies: ownedCopiesSql(set.id, filters.collectorId).mapWith(Number),
wishlistCount: wishlistCountSql(set.id, filters.collectorId).mapWith(Number),
})
.from(set)
.innerJoin(manufacturer, eq(manufacturer.id, set.manufacturerId))
@@ -126,7 +128,7 @@ export function listSetIds(filters: CatalogFilters): string[] {
.map((row) => row.id);
}
export function getSetById(id: string) {
export function getSetById(id: string, collectorId?: string) {
const row = db
.select({
id: set.id,
@@ -148,8 +150,8 @@ export function getSetById(id: string) {
notes: set.notes,
createdAt: set.createdAt,
updatedAt: set.updatedAt,
ownedCopies: ownedCopiesSql(set.id).mapWith(Number),
wishlistCount: wishlistCountSql(set.id).mapWith(Number),
ownedCopies: ownedCopiesSql(set.id, collectorId).mapWith(Number),
wishlistCount: wishlistCountSql(set.id, collectorId).mapWith(Number),
})
.from(set)
.innerJoin(manufacturer, eq(manufacturer.id, set.manufacturerId))