import type { CheckResult, DocumentStatus, FileOut, FileType, FileWarningOut, IssueCountsOut, Severity } from '../../types/api'; import { formatMoscowDate } from '../../utils/dates'; import { documentStatusLabel, documentTypeLabel, periodLabel } from '../../utils/labels'; import { toFileWarnings, warningsLevel } from './fileWarnings'; export type RowStatus = 'success' | 'warning' | 'processing' | 'error'; export interface DocumentRow { key: string; /** id документа; null — строка файла без документов */ documentId: string | null; /** id файла: удаление и замена — файлом целиком */ fileId: string; name: string; fileType: FileType; subtitle: string; period: string; periodCheck?: CheckResult | null; counterpartyCheck?: CheckResult | null; status: RowStatus; statusLabel: string; errorReason: string | null; linkable: boolean; /** предупреждения разбора файла (общие для всех документов файла) */ fileWarnings: FileWarningOut[]; mappingWarnings: string[]; /** счётчики issues документа; у строки файла без документов — нули */ issueCounts: IssueCountsOut; /** число комментариев документа; у строки файла без документов — 0 */ commentsCount: number; } /** Значок у статуса (P6-12): число предупреждений и цвет уровня; ничего нет — null. */ export function rowBadge(row: DocumentRow): { count: number; level: Severity } | null { const count = row.fileWarnings.length + row.issueCounts.error + row.issueCounts.info; if (count === 0) return null; const level = warningsLevel(row.fileWarnings) === 'error' || row.issueCounts.error > 0 ? 'error' : 'info'; return { count, level }; } const LINKABLE = new Set(['OSV', 'BALANCE', 'INCOME']); const join = (parts: (string | null)[]) => parts.filter(Boolean).join(' · '); function errorReason(f: FileOut): string | null { if (f.status !== 'ERROR' || !f.job) return null; return f.job.mapping_error ?? f.job.error_message ?? f.job.error_code ?? null; } function documentRowStatus(status: DocumentStatus): RowStatus { if (status === 'CONFIRMED') return 'success'; if (status === 'ERROR') return 'error'; return 'warning'; } /** Файлы → строки таблицы: одна строка на документ, файл без документов — одна строка на файл. */ export function buildDocumentRows(files: FileOut[], labelFor?: (f: FileOut) => string | null): DocumentRow[] { return files.flatMap((f): DocumentRow[] => { const extra = labelFor?.(f) ?? null; const name = f.original_name ?? 'без имени'; const date = formatMoscowDate(f.uploaded_at); const fileWarnings = toFileWarnings(f.job?.warnings); const mappingWarnings = f.job?.mapping_warnings ?? []; if (f.documents.length === 0) { const failed = f.status === 'ERROR'; return [ { key: f.id, documentId: null, fileId: f.id, name, fileType: f.file_type, subtitle: join([f.file_type, date, extra]), period: '—', status: failed ? 'error' : 'processing', statusLabel: failed ? 'Ошибка' : 'Обработка', errorReason: errorReason(f), linkable: false, fileWarnings, mappingWarnings, issueCounts: { error: 0, info: 0 }, commentsCount: 0, }, ]; } return f.documents.map((d) => { const typeText = d.document_type === 'DISCLOSURE' || d.document_type === 'OTHER' ? 'Прочий документ' : documentTypeLabel(d.document_type, d.osv_account_code); const status = documentRowStatus(d.status); return { key: d.id, documentId: d.id, fileId: f.id, name, fileType: f.file_type, subtitle: join([typeText, f.file_type, date, extra]), period: periodLabel(d.period_year, d.period_quarter, d.is_cumulative), periodCheck: d.period_check, counterpartyCheck: d.counterparty_check, status, statusLabel: documentStatusLabel(d.status), errorReason: null, linkable: LINKABLE.has(d.document_type), fileWarnings, mappingWarnings, // ядро без v5.19 не присылает issue_counts — считаем, что замечаний нет issueCounts: d.issue_counts ?? { error: 0, info: 0 }, commentsCount: d.comments_count ?? 0, }; }); }); }