- Дополнены типы в 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
49 lines
1.9 KiB
TypeScript
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`);
|
|
}
|