import prisma from "./prisma"; export interface EffectiveSetting { category: string; key: string; value: unknown; source: "user" | "tenant" | "customer" | "default"; dataType: string; description: string | null; allowedValues: unknown; } /** * Resolve effective settings using the 3-level cascade: * definition default → customer override → tenant override → user override * * Only user-overridable settings are included. */ export async function getUserEffectiveSettings( userId: string, tenantId: string, customerId: string | undefined, categories: string[] ): Promise>> { // 1. Fetch all definitions for requested categories const definitions = await prisma.settingsDefinition.findMany({ where: { category: { in: categories }, allow_user_override: true }, orderBy: [{ category: "asc" }, { display_order: "asc" }], }); // 2. Fetch customer overrides const customerSettings = customerId ? await prisma.customerSetting.findMany({ where: { customerId, settingId: { in: definitions.map((d) => d.id) }, }, }) : []; // 3. Fetch tenant overrides const tenantSettings = await prisma.tenantSetting.findMany({ where: { tenantId, settingId: { in: definitions.map((d) => d.id) }, }, }); // 4. Fetch user overrides const userSettings = await prisma.userSetting.findMany({ where: { userId, settingId: { in: definitions.map((d) => d.id) }, }, }); // Build lookup maps const custMap = new Map(customerSettings.map((s) => [s.settingId, s])); const tenantMap = new Map(tenantSettings.map((s) => [s.settingId, s])); const userMap = new Map(userSettings.map((s) => [s.settingId, s])); // 5. Resolve cascade const result: Record> = {}; for (const def of definitions) { let value: unknown = def.defaultValue; let source: EffectiveSetting["source"] = "default"; const custSetting = custMap.get(def.id); if (custSetting) { value = custSetting.value; source = "customer"; } const tenantSetting = tenantMap.get(def.id); if (tenantSetting) { // Only override if customer didn't mark as mandatory if (!custSetting?.is_mandatory || custSetting.allow_tenant_override) { value = tenantSetting.value; source = "tenant"; } } const userSetting = userMap.get(def.id); if (userSetting) { // Only override if tenant didn't mark as mandatory const canUserOverride = tenantSetting ? tenantSetting.allow_user_override !== false : true; if (canUserOverride) { value = userSetting.value; source = "user"; } } if (!result[def.category]) result[def.category] = {}; result[def.category][def.key] = { category: def.category, key: def.key, value, source, dataType: def.dataType, description: def.description, allowedValues: def.allowed_values, }; } return result; } /** * Batch upsert user settings. Each entry is { category, key, value }. */ export async function batchUpsertUserSettings( userId: string, settings: Array<{ category: string; key: string; value: unknown }> ) { // Look up setting definition IDs const keys = settings.map((s) => ({ category: s.category, key: s.key })); const definitions = await prisma.settingsDefinition.findMany({ where: { OR: keys.map((k) => ({ category: k.category, key: k.key })), }, select: { id: true, category: true, key: true }, }); const defMap = new Map(definitions.map((d) => [`${d.category}:${d.key}`, d.id])); // Build upserts const operations = settings .map((s) => { const settingId = defMap.get(`${s.category}:${s.key}`); if (!settingId) return null; return prisma.userSetting.upsert({ where: { userId_settingId: { userId, settingId } }, update: { value: s.value as never }, create: { userId, settingId, value: s.value as never }, }); }) .filter(Boolean) as ReturnType[]; await prisma.$transaction(operations); } /** * Get settings definitions for given categories. */ export async function getSettingsDefinitions(categories: string[]) { return prisma.settingsDefinition.findMany({ where: { category: { in: categories } }, orderBy: [{ category: "asc" }, { display_order: "asc" }], }); }