Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
/** Тарифы (ТЗ, раздел 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<Plan, PlanSpec> = {
|
|
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';
|