feat(entitlements): delad hushålls-pott – medlemmar delar ägarens plan & skanningskvot (MIKRO 59)
CI / Typecheck, test & build (push) Failing after 43s

This commit is contained in:
Sven (AAMOS AI)
2026-08-14 20:12:37 +07:00
parent 9bd827a90d
commit 83dd0bb215
+26 -6
View File
@@ -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<string> {
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<Entitlements> {
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<En
.from(schema.aiUsageCounters)
.where(
and(
eq(schema.aiUsageCounters.userId, userId),
eq(schema.aiUsageCounters.userId, billingUserId),
eq(schema.aiUsageCounters.month, currentMonth()),
),
)
@@ -69,7 +88,8 @@ export async function loadEntitlementsWithToken(
/** Kräv AI-kvot och räkna upp användningen atomiskt. */
export async function consumeAiScan(db: Database, userId: string): Promise<void> {
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<void>
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` },