133 lines
4 KiB
TypeScript
133 lines
4 KiB
TypeScript
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<number> {
|
|
const db = await getDb();
|
|
const rows = await db
|
|
.select({ count: sql<number>`COUNT(*)` })
|
|
.from(clinics)
|
|
.where(eq(clinics.userId, userId));
|
|
return Number(rows[0]?.count ?? 0);
|
|
}
|
|
|
|
async function countQueueEntriesTodayForClinic(clinicId: number): Promise<number> {
|
|
const db = await getDb();
|
|
const startOfDay = new Date();
|
|
startOfDay.setHours(0, 0, 0, 0);
|
|
const rows = await db
|
|
.select({ count: sql<number>`COUNT(*)` })
|
|
.from(queueEntries)
|
|
.where(
|
|
and(eq(queueEntries.clinicId, clinicId), gte(queueEntries.joinedAt, startOfDay))
|
|
);
|
|
return Number(rows[0]?.count ?? 0);
|
|
}
|
|
|
|
const FEATURE_MESSAGES: Record<PlanFeature, string> = {
|
|
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<PlanLimitCheck> {
|
|
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) };
|
|
}
|