import type { AdminTenant as ApiAdminTenant, AdminTenantDetailResponse, ApiTenantActivityEvent, ApiTenantBalanceTx, ApiTenantProject, ApiTenantUser, } from '../api/admin'; import type { AdminTenantDetail, TenantActivityEvent, TenantBalanceTx, TenantProject, TenantUser, } from './mockTenantDetail'; import type { TenantStatus, TenantTariff } from './mockTenants'; /** * Маппит AdminTenantDetailResponse (API) → AdminTenantDetail (UI mock-формат). * * Поля отсутствующие в API/schema (inn, contact_phone, legal_address, role) — * подставляются пустыми/'—'/'manager'-default'ом. На MVP UI показывает «—» * через v-if; в Post-MVP добавим legal_entities-связку. * * `code` ← tenant.subdomain (естественный URL slug). */ const TARIFF_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, isTrial: boolean): TenantTariff { if (isTrial) return 'Trial'; if (name === null) return TARIFF_FALLBACK; const match = KNOWN_TARIFFS.find((t) => t === name); return match ?? TARIFF_FALLBACK; } function activitySinceText(lastActivityIso: string | null, now: Date): string { if (lastActivityIso === null) return 'не активен'; const minutesAgo = Math.floor((now.getTime() - new Date(lastActivityIso).getTime()) / 60_000); if (minutesAgo < 1) return 'только что'; if (minutesAgo < 60) return `${minutesAgo} мин назад`; if (minutesAgo < 60 * 24) return `${Math.floor(minutesAgo / 60)} ч назад`; return `${Math.floor(minutesAgo / (60 * 24))} д назад`; } function mapUser(u: ApiTenantUser): TenantUser { const fullName = [u.first_name, u.last_name].filter(Boolean).join(' ').trim(); return { id: u.id, email: u.email, fullName: fullName !== '' ? fullName : u.email, // role в schema users отсутствует — на MVP единая роль 'manager'. // Расширение в legal_entities/user_roles вынесено в Post-MVP. role: 'manager', last_active_at: u.last_active_at ?? '', is_active: u.is_active, }; } function mapProject(p: ApiTenantProject): TenantProject { return { id: p.id, name: p.name, slug: p.tag ?? '', suppliers: p.suppliers_count, leadsToday: p.leads_today, desiredToday: p.daily_limit_target, is_active: p.is_active, }; } const TX_TYPE_MAP: Record = { topup: 'topup', lead_charge: 'lead_charge', refund: 'refund', manual_adjustment: 'manual_adjustment', // schema-only типы → ближайший UI-эквивалент. chargeback_writedown: 'manual_adjustment', chargeback_repayment: 'topup', trial_bonus: 'topup', historical_import: 'manual_adjustment', }; function mapBalanceTx(tx: ApiTenantBalanceTx): TenantBalanceTx { return { id: `TX-${tx.id}`, type: TX_TYPE_MAP[tx.type] ?? 'manual_adjustment', amount: parseFloat(tx.amount_rub), description: tx.description ?? '', created_at: tx.created_at, }; } function mapActivity(ev: ApiTenantActivityEvent): TenantActivityEvent { const summary = buildActivitySummary(ev); return { id: ev.id, event: ev.event, actor: ev.actor_email ?? 'system', summary, created_at: ev.created_at, }; } function buildActivitySummary(ev: ApiTenantActivityEvent): string { if (ev.context && typeof ev.context === 'object') { const ctx = ev.context as Record; if (typeof ctx.from === 'string' && typeof ctx.to === 'string') { return `Сделка #${ev.deal_id}: ${ctx.from} → ${ctx.to}`; } } if (ev.deal_id > 0) { return `Сделка #${ev.deal_id}`; } return ev.event; } export function mapAdminTenantDetail(api: AdminTenantDetailResponse, now: Date = new Date()): AdminTenantDetail { const status = deriveStatus(api.tenant); const balanceRub = parseFloat(api.tenant.balance_rub); return { // base AdminTenant id: api.tenant.id, code: api.tenant.subdomain, name: api.tenant.organization_name, inn: '', status, statusText: statusText(status), tariff: deriveTariff(api.tenant.tariff_name, api.tenant.is_trial), balanceRub, todayDesired: api.tenant.desired_daily_numbers ?? 0, todayActual: api.metrics.leads_today, mrrRub: api.tenant.mrr_rub !== null ? parseFloat(api.tenant.mrr_rub) : null, activitySince: activitySinceText(api.tenant.last_activity_at, now), // расширение AdminTenantDetail contact_email: api.tenant.contact_email, contact_phone: '', legal_address: '', created_at: api.tenant.created_at ?? '', users: api.users.map(mapUser), projects: api.projects.map(mapProject), balanceHistory: api.balance_history.map(mapBalanceTx), activity: api.activity.map(mapActivity), leadsThisMonth: api.metrics.leads_this_month, leadsThisWeek: api.metrics.leads_this_week, avgLeadCost: api.metrics.avg_lead_cost_rub ?? 0, runwayDays: api.metrics.runway_days ?? 0, }; }