arm-web/src/services/api/files.ts
Andrey Epifancev 4424a2b8e8 feat: типы DTO и сервисы API для файлов, документов, форм и свода (#3)
- Дополнены типы в src/types/api.ts (Page, UploadResult, FileOut, DocumentOut, StatementOut, ConsolidatedRowOut, etc.)
- Реализованы сервисы: uploadFiles, listLegalEntityFiles, fetchFileContent, getDocument, getStatement, getAllEntries, buildOsvQuery, getConsolidatedOsv, getOsvSummary, exportOsv
- Переписаны consolidations.ts (getVersionCard вместо getVersionData) и company_groups.ts (fetchRelatedGroups, fetchAllLegalEntities)
- Обновлены вызовы функций в компонентах и slices
- Добавлены unit-тесты files.test.ts и osv.test.ts (11 тестов, все PASSED)
- Типизация strict, без any, все тесты и сборка зелёные

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3
2026-10-08 00:59:20 +04:00

49 lines
1.9 KiB
TypeScript

import { ApiError, apiFetch, apiFetchBlob } from '../../api/client';
import type { FileOut, Page, UploadResult } from '../../types/api';
export function isUploadResult(body: unknown): body is UploadResult {
return (
typeof body === 'object' &&
body !== null &&
Array.isArray((body as UploadResult).accepted) &&
Array.isArray((body as UploadResult).rejected)
);
}
/** Загрузка файлов ЮЛ одним запросом; 422 «ни один не принят» возвращается как результат, прочие ошибки — throw. */
export async function uploadFiles(legalEntityId: string, files: File[], versionId: string | null): Promise<UploadResult> {
const form = new FormData();
for (const file of files) {
form.append('files', file);
}
const query = versionId ? `?consolidation_version_id=${encodeURIComponent(versionId)}` : '';
try {
return await apiFetch<UploadResult>(`/legal-entities/${legalEntityId}/files${query}`, { method: 'POST', body: form });
} catch (e: unknown) {
if (e instanceof ApiError && e.status === 422 && isUploadResult(e.body)) {
return e.body;
}
throw e;
}
}
export function summarizeUpload(result: UploadResult): { accepted: string[]; rejected: { name: string; reason: string }[] } {
return {
accepted: result.accepted.map((a) => a.name),
rejected: result.rejected.map((r) => ({ name: r.name, reason: r.message })),
};
}
export async function listLegalEntityFiles(legalEntityId: string): Promise<FileOut[]> {
const page = await apiFetch<Page<FileOut>>(`/legal-entities/${legalEntityId}/files?limit=100`);
return page.items;
}
export function hasPendingFiles(files: FileOut[]): boolean {
return files.some((f) => f.status === 'UPLOADED' || f.status === 'PROCESSING');
}
export function fetchFileContent(fileId: string): Promise<{ blob: Blob; filename: string | null }> {
return apiFetchBlob(`/files/${fileId}/content?disposition=inline`);
}