Files
portal/app/resources/js/api/reports.ts
T
Дмитрий e0ffe7e686 phase2(reports-stage4): frontend integration ReportsView (replace mock)
- api/reports.ts: типизированные axios-helpers (listReportJobs/createReportJob/
  retryReportJob/cancelReportJob/deleteReportJob) + ApiReportJob/Status/Format/
  Counts/Quota interfaces. ensureCsrfCookie на mutating-вызовах.
- composables/reportsMapper.ts: mapApiReportJob (API → UI mock format с конверсией
  pending→queued / processing→running). title строится на frontend'е (тип + period
  с RU-месяцами «апр 2026» или диапазон «мар 2026 — апр 2026»). sizeText форматирует
  bytes (B/KB/MB). timeText зависит от status (в очереди / в работе · Nс / N мин назад).
  uiTypeToApi (deals → deals_export и т.д.).
- ReportsView.vue полностью переписан под API:
  - onMounted → loadJobs (replace MOCK_JOBS на data из listReportJobs).
  - usePolling 30 сек (фоновый авто-refresh).
  - Submit → createReportJob → reload + success-alert + error-alert (validation+
    общие ошибки извлекаются через extractValidationErrors/extractErrorMessage).
  - canSubmit computed: disable если квота заполнена (active >= max).
  - Reset-btn возвращает форму к defaults.
  - Reload-btn (manual fast-path).
  - Retry/Cancel/Download/Delete-кнопки → соответствующие API-вызовы;
    Delete через v-dialog persistent confirm.
  - fetch-error-alert на listReportJobs reject.
  - Empty-state «Нет отчётов» когда jobs.length=0.
  - canRetry проверяет retry_count<3 (max attempts CTO-6).
- Vitest +24 (всего 393/393, +24 от 369):
  - reportsMapper.spec.ts +14: status mapping (pending/processing/done/failed) /
    title (один месяц / диапазон) / format / sizeText (B/KB/MB/null) / attempt /
    error / timeText (pending / processing / done «10 мин назад» / «только что») /
    uiTypeToApi 4 slug'а / progress=50 для running.
  - ReportsView.spec.ts переписан с MOCK_JOBS на vi.mock('api/reports') +12:
    mount + listReportJobs called on mount / 4 type cards / default Сделки active /
    4 формата / quota-banner из API / empty-state / done с Готов+Скачать /
    failed с Ошибка+Повторить / failed retry_count=3 НЕ показывает Повторить /
    pending с Отменить / Submit вызывает createReportJob+reload / Submit error →
    submit-error-alert / Submit-btn disabled при квоте 3/3 / Reset / Reload-btn /
    fetch-error-alert / Retry-btn / Cancel-btn / Delete confirm-dialog +
    deleteReportJob.

Этап 4/4 эпика Reports backend ЗАКРЫТ. Эпик закрыт целиком.

Backend: 1 type (deals_export) × 4 формата (CSV/XLSX/JSON/PDF-stub).
Этап 2b (3 оставшихся типа: managers_summary/sources_summary/billing_summary)
— расширение через добавление 3 новых Provider-классов без изменений в архитектуре,
вынесено в Post-MVP backlog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:49:55 +03:00

117 lines
3.2 KiB
TypeScript

import { apiClient, ensureCsrfCookie } from './client';
/**
* Reports API (schema §13.5 report_jobs).
*
* Все endpoint'ы под Sanctum SPA auth. Mutating-вызовы (POST/DELETE)
* делают ensureCsrfCookie().
*
* Backend status: pending|processing|done|failed (schema-канон).
* UI mock использует queued|running|done|failed — конверсия в reportsMapper.
*/
export type ApiReportStatus = 'pending' | 'processing' | 'done' | 'failed';
export type ApiReportType = 'deals_export' | 'managers_summary' | 'sources_summary' | 'billing_summary';
export type ApiReportFormat = 'csv' | 'xlsx' | 'json' | 'pdf';
export interface ApiReportParameters {
format: ApiReportFormat;
date_from: string;
date_to: string;
project_id?: number | null;
manager_id?: number | null;
retry_count?: number;
retry_of?: number;
}
export interface ApiReportJob {
id: number;
type: ApiReportType;
parameters: ApiReportParameters;
status: ApiReportStatus;
file_path: string | null;
file_size: number | null;
generation_seconds: number | null;
error_message: string | null;
created_at: string | null;
finished_at: string | null;
expires_at: string | null;
is_expired: boolean;
retry_count: number;
retry_max: number;
}
export interface ReportCounts {
pending: number;
processing: number;
done: number;
failed: number;
}
export interface ReportQuota {
active: number;
max_active: number;
}
export interface ListReportJobsResponse {
jobs: ApiReportJob[];
total: number;
limit: number;
offset: number;
counts: ReportCounts;
quota: ReportQuota;
}
export interface ListReportJobsParams {
status?: ApiReportStatus;
limit?: number;
offset?: number;
}
export async function listReportJobs(params: ListReportJobsParams = {}): Promise<ListReportJobsResponse> {
const { data } = await apiClient.get<ListReportJobsResponse>('/api/reports/jobs', {
params: {
status: params.status,
limit: params.limit,
offset: params.offset,
},
});
return data;
}
export interface CreateReportJobPayload {
type: ApiReportType;
format: ApiReportFormat;
parameters: {
date_from: string;
date_to: string;
project_id?: number | null;
manager_id?: number | null;
};
}
export async function createReportJob(payload: CreateReportJobPayload): Promise<ApiReportJob> {
await ensureCsrfCookie();
const { data } = await apiClient.post<{ job: ApiReportJob }>('/api/reports/jobs', payload);
return data.job;
}
export async function retryReportJob(id: number): Promise<ApiReportJob> {
await ensureCsrfCookie();
const { data } = await apiClient.post<{ job: ApiReportJob }>(`/api/reports/jobs/${id}/retry`);
return data.job;
}
export async function cancelReportJob(id: number): Promise<ApiReportJob> {
await ensureCsrfCookie();
const { data } = await apiClient.post<{ job: ApiReportJob }>(`/api/reports/jobs/${id}/cancel`);
return data.job;
}
export async function deleteReportJob(id: number): Promise<void> {
await ensureCsrfCookie();
await apiClient.delete(`/api/reports/jobs/${id}`);
}