Lead Radar: исходный код

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
yuriandClaude Opus 5.5 committed 2026-10-25 23:11:04 +04:00
commit 9749da5ae7
201 files changed
+97685

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
/** Тарифы (ТЗ, раздел 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';