import { and, eq, gte } from "drizzle-orm"; import { getDb, getSubscription } from "../db.js"; import { clinics, queueEntries } from "../schema.js"; import { sql } from "drizzle-orm"; export type PlanFeature = | "maxClinics" | "maxQueueEntriesPerDay" | "multiPractitioner" | "analyticsExport"; type PlanLimits = { maxClinics: number; maxQueueEntriesPerDay: number; multiPractitioner: boolean; analyticsExport: boolean; }; // trial == basic level access during trial period; pro lifts everything. export const PLAN_LIMITS: Record<"trial" | "basic" | "pro", PlanLimits> = { trial: { maxClinics: 1, maxQueueEntriesPerDay: 50, multiPractitioner: false, analyticsExport: false, }, basic: { maxClinics: 1, maxQueueEntriesPerDay: 200, multiPractitioner: false, analyticsExport: true, }, pro: { maxClinics: Infinity, maxQueueEntriesPerDay: Infinity, multiPractitioner: true, analyticsExport: true, }, }; export type PlanLimitCheck = | { ok: true } | { ok: false; reason: string; feature: PlanFeature }; async function getUserPlan(userId: number): Promise<"trial" | "basic" | "pro"> { const sub = await getSubscription(userId); return sub?.plan ?? "trial"; } function limits(plan: "trial" | "basic" | "pro"): PlanLimits { return PLAN_LIMITS[plan]; } async function countClinicsForUser(userId: number): Promise { const db = await getDb(); const rows = await db .select({ count: sql`COUNT(*)` }) .from(clinics) .where(eq(clinics.userId, userId)); return Number(rows[0]?.count ?? 0); } async function countQueueEntriesTodayForClinic(clinicId: number): Promise { const db = await getDb(); const startOfDay = new Date(); startOfDay.setHours(0, 0, 0, 0); const rows = await db .select({ count: sql`COUNT(*)` }) .from(queueEntries) .where( and(eq(queueEntries.clinicId, clinicId), gte(queueEntries.joinedAt, startOfDay)) ); return Number(rows[0]?.count ?? 0); } const FEATURE_MESSAGES: Record = { maxClinics: "Limite de cabinets atteinte : passez au plan Pro pour en créer plus.", maxQueueEntriesPerDay: "Limite quotidienne de patients atteinte : passez au plan Pro pour des inscriptions illimitées.", multiPractitioner: "La gestion multi-praticiens est réservée au plan Pro. Mettez à niveau pour ajouter des praticiens.", analyticsExport: "L'export des statistiques est réservé aux plans payants. Mettez à niveau pour exporter vos données.", }; export async function checkPlanLimit( userId: number, feature: PlanFeature, context: { clinicId?: number } = {} ): Promise { const plan = await getUserPlan(userId); const max = limits(plan); switch (feature) { case "maxClinics": { if (max.maxClinics === Infinity) return { ok: true }; const current = await countClinicsForUser(userId); if (current >= max.maxClinics) { return { ok: false, feature, reason: FEATURE_MESSAGES.maxClinics }; } return { ok: true }; } case "maxQueueEntriesPerDay": { if (max.maxQueueEntriesPerDay === Infinity) return { ok: true }; if (!context.clinicId) return { ok: true }; const today = await countQueueEntriesTodayForClinic(context.clinicId); if (today >= max.maxQueueEntriesPerDay) { return { ok: false, feature, reason: FEATURE_MESSAGES.maxQueueEntriesPerDay, }; } return { ok: true }; } case "multiPractitioner": { if (max.multiPractitioner) return { ok: true }; return { ok: false, feature, reason: FEATURE_MESSAGES.multiPractitioner }; } case "analyticsExport": { if (max.analyticsExport) return { ok: true }; return { ok: false, feature, reason: FEATURE_MESSAGES.analyticsExport }; } } } export async function getPlanLimitsForUser(userId: number): Promise<{ plan: "trial" | "basic" | "pro"; limits: PlanLimits; }> { const plan = await getUserPlan(userId); return { plan, limits: limits(plan) }; }