import { apiClient, ensureCsrfCookie } from './client'; /** * API-вызовы для админских endpoint'ов SaaS (см. ImpersonationController). * * На MVP вызываются без auth:saas-admin middleware (см. routes/web.php). * Production: middleware('auth:saas-admin') + cookie session — apiClient уже * настроен на withCredentials. */ export interface ImpersonationInitPayload { tenant_id: number; requested_by: number; // на MVP параметром; на prod — request()->user()->id reason: string; // ≥30 chars (валидируется на backend) } export interface ImpersonationInitResponse { token_id: number; expires_at: string; // ISO8601 sent_to_email: string; /** dev-only: исчезнет после интеграции MailService на prod */ _dev_plain_code?: string; } export async function impersonationInit(payload: ImpersonationInitPayload): Promise { await ensureCsrfCookie(); const { data } = await apiClient.post('/api/admin/impersonation/init', payload); return data; } export interface ImpersonationVerifyPayload { token_id: number; code: string; // 6 цифр } export interface ImpersonationVerifyResponse { token_id: number; tenant_id: number; used_at: string; message: string; } export async function impersonationVerify(payload: ImpersonationVerifyPayload): Promise { await ensureCsrfCookie(); const { data } = await apiClient.post('/api/admin/impersonation/verify', payload); return data; } export interface ImpersonationEndResponse { token_id: number; session_ended_at: string; message: string; } export async function impersonationEnd(tokenId: number): Promise { await ensureCsrfCookie(); const { data } = await apiClient.post('/api/admin/impersonation/end', { token_id: tokenId, }); return data; } export interface ImpersonationActiveSession { token_id: number; tenant_id: number; tenant_name: string | null; requested_by: number; reason: string; sent_to_email: string; used_at: string; expires_at: string; } export interface ImpersonationRecentSession { token_id: number; tenant_id: number; tenant_name: string | null; requested_by: number; reason: string; used_at: string; session_ended_at: string; duration_seconds: number | null; } export async function impersonationActive(): Promise { const { data } = await apiClient.get<{ sessions: ImpersonationActiveSession[] }>('/api/admin/impersonation/active'); return data.sessions; } export async function impersonationRecent(): Promise { const { data } = await apiClient.get<{ sessions: ImpersonationRecentSession[] }>('/api/admin/impersonation/recent'); return data.sessions; } // === SaaS-admin → Тенанты: lookup для AdminTenantsView === export interface AdminTenant { id: number; subdomain: string; organization_name: string; contact_email: string; status: string; balance_rub: string; balance_leads: number; is_trial: boolean; last_activity_at: string | null; tariff_id: number | null; tariff_name: string | null; /** price_monthly активного тарифа если не-trial; иначе null. */ mrr_rub: string | null; desired_daily_numbers: number | null; chargeback_unrecovered_rub: string; created_at: string | null; } interface AdminTenantsStats { total: number; active: number; trial: number; overdue: number; } export interface ListAdminTenantsParams { status?: string; search?: string; limit?: number; offset?: number; } export interface ListAdminTenantsResponse { tenants: AdminTenant[]; total: number; limit: number; offset: number; stats: AdminTenantsStats; } export async function listAdminTenants(params: ListAdminTenantsParams = {}): Promise { const { data } = await apiClient.get('/api/admin/tenants', { params }); return data; } // === SaaS-admin → Тенанты → детали (для AdminTenantDetailView) === export interface ApiTenantUser { id: number; email: string; first_name: string | null; last_name: string | null; is_active: boolean; totp_enabled: boolean; last_active_at: string | null; last_login_at: string | null; } export interface ApiTenantProject { id: number; name: string; tag: string | null; is_active: boolean; daily_limit_target: number; suppliers_count: number; leads_today: number; } export interface ApiTenantBalanceTx { id: number; type: string; amount_rub: string; amount_leads: number; balance_rub_after: string | null; description: string | null; created_at: string; } export interface ApiTenantActivityEvent { id: number; event: string; deal_id: number; actor_email: string | null; context: Record | null; created_at: string; } interface ApiTenantMetrics { leads_today: number; leads_this_week: number; leads_this_month: number; avg_lead_cost_rub: number | null; runway_days: number | null; } export interface AdminTenantDetailResponse { tenant: AdminTenant; users: ApiTenantUser[]; projects: ApiTenantProject[]; balance_history: ApiTenantBalanceTx[]; activity: ApiTenantActivityEvent[]; metrics: ApiTenantMetrics; } export async function getAdminTenantDetail(subdomain: string): Promise { const { data } = await apiClient.get( `/api/admin/tenants/${encodeURIComponent(subdomain)}`, ); return data; } // === SaaS-admin → Биллинг: aggregates пополнений/списаний === export interface ApiAdminBillingTenant { id: number; subdomain: string; organization_name: string; contact_email: string; status: string; balance_rub: string; tariff_id: number | null; tariff_name: string | null; mrr_rub: string; monthly_topups_rub: string; monthly_charges_rub: string; last_payment_at: string | null; chargeback_unrecovered_rub: string; } interface ApiAdminBillingSummary { total_mrr_rub: string; monthly_revenue_rub: string; overdue_count: number; refunds_count_30d: number; } export interface ListAdminBillingResponse { tenants: ApiAdminBillingTenant[]; summary: ApiAdminBillingSummary; } export async function listAdminBilling(search = ''): Promise { const { data } = await apiClient.get('/api/admin/billing', { params: { search }, }); return data; } // === SaaS-admin → Инциденты === export interface ApiAdminIncident { id: number; incident_id: string; type: string; severity: 'low' | 'medium' | 'high' | 'critical'; summary: string; started_at: string; detected_at: string; resolved_at: string | null; status: 'open' | 'investigating' | 'resolved'; affected_tenants_count: number; affected_users_count: number | null; rkn_notified: boolean; rkn_notified_at: string | null; rkn_deadline_at: string | null; } interface ApiAdminIncidentsSummary { open: number; investigating: number; rkn_pending: number; total_unresolved: number; } export interface ListAdminIncidentsParams { type?: string; severity?: string; unresolved_only?: boolean; limit?: number; offset?: number; } export interface ListAdminIncidentsResponse { incidents: ApiAdminIncident[]; total: number; limit: number; offset: number; summary: ApiAdminIncidentsSummary; } export async function listAdminIncidents(params: ListAdminIncidentsParams = {}): Promise { const { data } = await apiClient.get('/api/admin/incidents', { params }); return data; } // === SaaS-admin → Система: system_settings edit-flow === export interface SystemSetting { key: string; value: string; type: 'int' | 'string' | 'decimal' | 'bool' | 'json'; description: string | null; updated_at: string; updated_by: number | null; } export async function listSystemSettings(): Promise { const { data } = await apiClient.get<{ settings: SystemSetting[] }>('/api/admin/system-settings'); return data.settings; } export interface UpdateSystemSettingPayload { value: string; reason: string; // ≥30 chars admin_user_id: number; // на prod удалится } export interface UpdateSystemSettingResponse { key: string; value: string; previous_value: string; updated_at: string; message: string; } export async function updateSystemSetting( key: string, payload: UpdateSystemSettingPayload, ): Promise { await ensureCsrfCookie(); const { data } = await apiClient.put( `/api/admin/system-settings/${encodeURIComponent(key)}`, payload, ); return data; } // === SaaS-admin → Биллинг: row-actions (Sprint 3D G4) === export interface AdminTariffPlan { id: number; name: string; price_monthly: string; } export async function listAdminTariffPlans(): Promise { const { data } = await apiClient.get<{ plans: AdminTariffPlan[] }>('/api/admin/billing/tariff-plans'); return data.plans; } export async function updateTenantStatus( id: number, status: 'active' | 'suspended', reason: string, ): Promise<{ id: number; status: string }> { await ensureCsrfCookie(); const { data } = await apiClient.patch<{ id: number; status: string }>( `/api/admin/billing/tenants/${id}/status`, { status, reason }, ); return data; } export async function refundTenant( id: number, amountRub: number, reason: string, ): Promise<{ id: number; balance_rub: string; transaction_id: number }> { await ensureCsrfCookie(); const { data } = await apiClient.post<{ id: number; balance_rub: string; transaction_id: number }>( `/api/admin/billing/tenants/${id}/refund`, { amount_rub: amountRub, reason }, ); return data; } export async function updateTenantBalance( id: number, payload: { balance_rub: string; reason?: string }, ): Promise<{ id: number; balance_rub: string; delta: string; transaction_id: number }> { await ensureCsrfCookie(); const { data } = await apiClient.patch<{ id: number; balance_rub: string; delta: string; transaction_id: number; }>(`/api/admin/tenants/${id}/balance`, payload); return data; } export async function changeTenantTariff( id: number, tariffId: number, reason: string, ): Promise<{ id: number; tariff_id: number; tariff_name: string }> { await ensureCsrfCookie(); const { data } = await apiClient.patch<{ id: number; tariff_id: number; tariff_name: string }>( `/api/admin/billing/tenants/${id}/tariff`, { tariff_id: tariffId, reason }, ); return data; } // === SaaS-admin → Инциденты: detail-view + РКН-notify (Sprint 3D G5/G6) === export interface ApiIncidentAffectedTenant { id: number; organization_name: string; } export interface ApiAdminIncidentDetail { id: number; incident_id: string; type: string; severity: 'low' | 'medium' | 'high' | 'critical'; summary: string; root_cause: string | null; postmortem_url: string | null; started_at: string; detected_at: string; resolved_at: string | null; status: 'open' | 'investigating' | 'resolved'; affected_tenants: ApiIncidentAffectedTenant[]; affected_users_count: number | null; notification_sent_at: string | null; rkn_notified: boolean; rkn_notified_at: string | null; rkn_deadline_at: string | null; created_by_admin: string | null; closed_by_admin: string | null; created_at: string | null; updated_at: string | null; } export async function getAdminIncidentDetail(id: number): Promise { const { data } = await apiClient.get<{ incident: ApiAdminIncidentDetail }>(`/api/admin/incidents/${id}`); return data.incident; } export async function notifyIncidentRkn(id: number): Promise { await ensureCsrfCookie(); const { data } = await apiClient.post<{ incident: ApiAdminIncidentDetail }>( `/api/admin/incidents/${id}/rkn-notify`, {}, ); return data.incident; } // === SaaS-admin → Тарифная сетка (Plan 4 / Sprint 5C G3) === export interface AdminPricingTier { tier_no: number; leads_in_tier: number | null; price_per_lead_kopecks: number; effective_from: string; } export interface PricingTiersResponse { active: AdminPricingTier[]; scheduled: Record; } export interface PricingTierEditorRow { tier_no: number; leads_in_tier: number | null; price_rub: string; } export async function getPricingTiers(): Promise { const { data } = await apiClient.get<{ data: PricingTiersResponse }>('/api/admin/pricing-tiers'); return { active: data.data.active, scheduled: data.data.scheduled ?? {} }; } export async function createPricingTiers( tiers: PricingTierEditorRow[], effectiveFrom?: string, ): Promise<{ effective_from: string }> { await ensureCsrfCookie(); const payload: { tiers: PricingTierEditorRow[]; effective_from?: string } = { tiers }; if (effectiveFrom) payload.effective_from = effectiveFrom; const { data } = await apiClient.post<{ effective_from: string }>('/api/admin/pricing-tiers', payload); return data; } export async function deleteScheduledPricingTier(effectiveFrom: string): Promise { await ensureCsrfCookie(); await apiClient.delete(`/api/admin/pricing-tiers/scheduled/${effectiveFrom}`); } // === SaaS-admin → Цены поставщиков (Plan 4 / Sprint 5C G3) === export interface AdminSupplier { id: number; code: string; name: string; cost_rub: string; quality_score: string; is_active: boolean; } export async function getAdminSuppliers(): Promise { const { data } = await apiClient.get<{ data: AdminSupplier[] }>('/api/admin/suppliers'); return data.data; } export async function updateAdminSupplier( id: number, payload: { cost_rub: string; quality_score: string; is_active: boolean }, ): Promise { await ensureCsrfCookie(); const { data } = await apiClient.patch<{ data: AdminSupplier }>(`/api/admin/suppliers/${id}`, payload); return data.data; } // --------------------------------------------------------------------------- // 152-ФЗ: обращения субъектов ПДн // --------------------------------------------------------------------------- export interface PdSubjectRequest { id: number; received_at: string; subject_email: string | null; subject_phone: string | null; subject_full_name: string | null; request_type: 'access' | 'rectification' | 'deletion' | 'objection'; description: string | null; status: 'received' | 'in_progress' | 'completed' | 'rejected'; tenant_id: number | null; assigned_admin_id: number | null; response_text: string | null; deadline_at: string; completed_at: string | null; processing_restricted: boolean; } export interface ListPdRequestsResponse { data: PdSubjectRequest[]; total: number; limit: number; offset: number; } export interface CreatePdRequestPayload { subject_email?: string; subject_phone?: string; subject_full_name?: string; request_type: 'access' | 'rectification' | 'deletion' | 'objection'; description?: string; tenant_id?: number | null; } export interface EraseSubjectResult { message: string; counts: { users: number; leads: number; deals: number; webhook_log: number }; } export async function listPdSubjectRequests( params: { status?: string; request_type?: string; limit?: number; offset?: number } = {}, ): Promise { const { data } = await apiClient.get('/api/admin/pd-subject-requests', { params }); return data; } export async function createPdSubjectRequest(payload: CreatePdRequestPayload): Promise { await ensureCsrfCookie(); const { data } = await apiClient.post<{ data: PdSubjectRequest }>('/api/admin/pd-subject-requests', payload); return data.data; } export async function executePdErasure(id: number, adminUserId?: number): Promise { await ensureCsrfCookie(); const payload = adminUserId !== undefined ? { admin_user_id: adminUserId } : {}; const { data } = await apiClient.post( `/api/admin/pd-subject-requests/${id}/erase`, payload, ); return data; }