/** Тарифы (ТЗ, раздел 2). Лимиты и модули проверяются на сервере. */ export const PLANS = ['solo', 'team', 'agency'] as const; export type Plan = (typeof PLANS)[number]; export const BILLING_MODELS = ['fixed', 'percent'] as const; export type BillingModel = (typeof BILLING_MODELS)[number]; export const MODULES = ['web', 'analytics', 'archive'] as const; /** web — веб-кабинет; analytics — аналитика по сотрудникам; archive — база бывших клиентов и объектов. */ export type Module = (typeof MODULES)[number]; export interface PlanSpec { maxMembers: number; setupFeeUsd: number; monthlyUsd: number; /** Процент от комиссии по сделкам; null — модель «%» недоступна. */ commissionPercent: number | null; modules: readonly Module[]; } export const PLAN_SPECS: Record = { solo: { maxMembers: 1, setupFeeUsd: 0, monthlyUsd: 40, commissionPercent: null, modules: [] }, team: { maxMembers: 10, setupFeeUsd: 500, monthlyUsd: 350, commissionPercent: 10, modules: ['web', 'analytics', 'archive'], }, agency: { maxMembers: 35, setupFeeUsd: 750, monthlyUsd: 550, commissionPercent: 7, modules: ['web', 'analytics', 'archive'], }, }; export const hasModule = (plan: Plan, module: Module) => PLAN_SPECS[plan].modules.includes(module); /** Solo всегда на фиксе; у остальных модель выбирается. */ export const allowedBillingModels = (plan: Plan): readonly BillingModel[] => PLAN_SPECS[plan].commissionPercent === null ? ['fixed'] : BILLING_MODELS; /** Срок отсрочки по умолчанию: столько дней после окончания подписки сервис ещё работает. */ export const DEFAULT_GRACE_DAYS = 15; export const ACCESS_STATES = ['active', 'overdue', 'restricted', 'disabled'] as const; /** * active — подписка действует; overdue — просрочена, сервис работает; * restricted — отсрочка истекла: без уведомлений о лидах, CRM только на чтение; * disabled — доступ выключен суперадмином либо компания ещё не оплачена. */ export type AccessState = (typeof ACCESS_STATES)[number]; export function accessState( company: { accessEnabled: boolean; subscriptionEndsAt: Date | null; graceDays: number }, now = new Date(), ): AccessState { if (!company.accessEnabled || !company.subscriptionEndsAt) return 'disabled'; const end = company.subscriptionEndsAt.getTime(); if (now.getTime() <= end) return 'active'; return now.getTime() <= end + company.graceDays * 86_400_000 ? 'overdue' : 'restricted'; } export const canWrite = (state: AccessState) => state === 'active' || state === 'overdue';