From 83dd0bb21584dbf838d198776e55757adeb7db95 Mon Sep 17 00:00:00 2001 From: "Sven (AAMOS AI)" Date: Fri, 14 Aug 2026 20:12:37 +0700 Subject: [PATCH] =?UTF-8?q?feat(entitlements):=20delad=20hush=C3=A5lls-pot?= =?UTF-8?q?t=20=E2=80=93=20medlemmar=20delar=20=C3=A4garens=20plan=20&=20s?= =?UTF-8?q?kanningskvot=20(MIKRO=2059)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/lib/entitlements.ts | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/api/src/lib/entitlements.ts b/apps/api/src/lib/entitlements.ts index 50731b7..d340ff4 100644 --- a/apps/api/src/lib/entitlements.ts +++ b/apps/api/src/lib/entitlements.ts @@ -8,22 +8,41 @@ import { type SubscriptionSnapshot, type TrialSnapshot, } from "@app/subscriptions"; -import { currentMonth } from "./helpers.js"; +import { currentMonth, getActiveHouseholdId } from "./helpers.js"; import { errors } from "./errors.js"; +/** Vem "äger" kvoten? Hela hushållet delar ägarens plan + pott. + * Ensam användare = sig själv (äger sitt eget hushåll). */ +async function billingUserFor(db: Database, userId: string): Promise { + const householdId = await getActiveHouseholdId(db, userId); + if (!householdId) return userId; + const [owner] = await db + .select({ userId: schema.householdMembers.userId }) + .from(schema.householdMembers) + .where( + and( + eq(schema.householdMembers.householdId, householdId), + eq(schema.householdMembers.role, "owner"), + ), + ) + .limit(1); + return owner?.userId ?? userId; +} + /** Ladda och beräkna entitlements för en användare. Backend = source of truth (spec §61.14). */ export async function loadEntitlements(db: Database, userId: string): Promise { + const billingUserId = await billingUserFor(db, userId); const [subRow] = await db .select() .from(schema.subscriptions) - .where(eq(schema.subscriptions.userId, userId)) + .where(eq(schema.subscriptions.userId, billingUserId)) .orderBy(desc(schema.subscriptions.updatedAt)) .limit(1); const [trialRow] = await db .select() .from(schema.trials) - .where(eq(schema.trials.userId, userId)) + .where(eq(schema.trials.userId, billingUserId)) .limit(1); const [usageRow] = await db @@ -31,7 +50,7 @@ export async function loadEntitlements(db: Database, userId: string): Promise { - const ent = await loadEntitlements(db, userId); + const billingUserId = await billingUserFor(db, userId); + const ent = await loadEntitlements(db, billingUserId); if (ent.aiScansUsedThisMonth >= ent.aiScansPerMonth) { throw errors.quotaExceeded( `Månadens AI-skanningar är slut (${ent.aiScansPerMonth} st). Uppgradera för fler.`, @@ -78,7 +98,7 @@ export async function consumeAiScan(db: Database, userId: string): Promise const month = currentMonth(); await db .insert(schema.aiUsageCounters) - .values({ userId, month, aiScans: 1 }) + .values({ userId: billingUserId, month, aiScans: 1 }) .onConflictDoUpdate({ target: [schema.aiUsageCounters.userId, schema.aiUsageCounters.month], set: { aiScans: (await import("drizzle-orm")).sql`${schema.aiUsageCounters.aiScans} + 1` },