/** * Маппер `AdminTenant` (api/admin.ts) → `AdminTenant` (mockTenants.ts UI-формат). * * Backend-схема (`tenants`) и UI-форма расходятся: * - status — schema: active/suspended/pending_email_confirm/deleted; * UI-форма ожидает: active/trial/overdue/suspended. * → derived: is_trial=true → 'trial'; balance<0 || chargeback>0 → 'overdue'; * schema 'active'/'suspended' → as-is; иначе → 'suspended'. * - inn — отсутствует в `tenants` (живёт в `legal_entities` для оператора SaaS * и в `invoices.payer_inn` для покупателей). На MVP отдаём пустую строку. * - todayActual — нет в API (требует JOIN на deals + GROUP BY DATE(received_at)). * На MVP 0; добавим отдельным endpoint'ом если понадобится. * - mrrRub — приходит из API как `tariff.price_monthly` (string, если * не-trial); парсим в number. null для trial и tenant'ов без активного * тарифа. * - code — берём subdomain как «slug»-эквивалент. */ import type { AdminTenant as ApiAdminTenant } from '../api/admin'; import type { AdminTenant, TenantStatus, TenantTariff } from './mockTenants'; const TARIFF_NAME_FALLBACK: TenantTariff = 'Trial'; const KNOWN_TARIFFS: TenantTariff[] = ['Trial', 'Start', 'Команда', 'Pro', 'Enterprise']; function deriveStatus(api: ApiAdminTenant): TenantStatus { const balance = parseFloat(api.balance_rub); const chargeback = parseFloat(api.chargeback_unrecovered_rub); if (api.is_trial) return 'trial'; if (api.status === 'suspended') return 'suspended'; if (chargeback > 0 || balance < 0) return 'overdue'; if (api.status === 'active') return 'active'; return 'suspended'; } function statusText(status: TenantStatus): string { const map: Record = { active: 'Активен', trial: 'Trial', overdue: 'Просрочка', suspended: 'Приостановлен', }; return map[status]; } function deriveTariff(name: string | null): TenantTariff { if (name === null) return TARIFF_NAME_FALLBACK; const match = KNOWN_TARIFFS.find((t) => t === name); return match ?? TARIFF_NAME_FALLBACK; } function formatRelative(iso: string | null, now: Date = new Date()): string { if (iso === null) return '—'; const dt = new Date(iso); const minutes = Math.max(0, Math.floor((now.getTime() - dt.getTime()) / 60000)); if (minutes < 60) return `${minutes} мин назад`; if (minutes < 60 * 24) return `${Math.floor(minutes / 60)} ч назад`; return `${Math.floor(minutes / (60 * 24))} д назад`; } export function mapApiAdminTenant(api: ApiAdminTenant, now: Date = new Date()): AdminTenant { const status = deriveStatus(api); return { id: api.id, code: api.subdomain, name: api.organization_name, inn: '', // нет в API status, statusText: statusText(status), tariff: deriveTariff(api.tariff_name), balanceRub: parseFloat(api.balance_rub), todayDesired: api.desired_daily_numbers ?? 0, todayActual: 0, // нет в API (потребует aggregate query) mrrRub: api.mrr_rub !== null ? parseFloat(api.mrr_rub) : null, activitySince: formatRelative(api.last_activity_at, now), }; }