feat(types): API формы v5.19 — статусы строк, needs_review, issues, предупреждения файла (#43)
- types/api.ts: LineStatus, Severity, IssueKind, PeriodKind, FileWarningOut, IssueOut, EditionLineOut, IssueCountsOut, CellTarget, LinePatch, ColumnPatch; новые поля StatementHeaderOut, ColumnOut, ValueOut, LineOut, FailedCheckOut, StatementOut (form_variant, issues, edition_lines), DocumentBrief (issue_counts), DocumentOut (issues), JobBrief.warnings — FileWarningOut[]. - statementGrid.ts: GridRow получает статус строки, номер строки, код как распознан, флаги needs_review / is_confirmed / codeModified; cellFlags: внимание только по needs_review, новый флаг confirmed. - labels.ts: lineStatusLabel, severityLabel. - фикстура __fixtures__/statement-v2.json (БАЛАНС 2025 Q3, NEEDS_CHECK): 16 строк × 2 колонки, у каждой пары ValueOut. - тесты: переносы полей v2 и покрытие пар строка × колонка, cellFlags по needs_review, подписи; хелперы row() / v() / gridRow / doc() дополнены значениями по умолчанию. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
0f2bbc80c9
commit
f83b03543e
@ -28,6 +28,25 @@ export interface UploadResult {
|
|||||||
rejected: RejectedOut[];
|
rejected: RejectedOut[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Статусы строки формы (v5.19): ok — строка бланка; unreadable — код не распознан; missing — кода нет; detail — расшифровка. */
|
||||||
|
export type LineStatus = 'ok' | 'unreadable' | 'missing' | 'detail';
|
||||||
|
export type Severity = 'error' | 'info';
|
||||||
|
export type IssueKind = 'warning' | 'relation' | 'soft';
|
||||||
|
export type PeriodKind =
|
||||||
|
| 'absolute'
|
||||||
|
| 'relative:current'
|
||||||
|
| 'relative:prev_year_end'
|
||||||
|
| 'relative:prev_prev_year_end'
|
||||||
|
| 'relative:prev_period'
|
||||||
|
| 'none';
|
||||||
|
/** Предупреждение разбора файла (`job.warnings`); `mapping_warnings` остаются строками. */
|
||||||
|
export interface FileWarningOut {
|
||||||
|
code: string;
|
||||||
|
severity: Severity;
|
||||||
|
message: string;
|
||||||
|
locator: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export type RecognitionStatus = string; // pending | processing | done | error | timeout | dlq — только для показа
|
export type RecognitionStatus = string; // pending | processing | done | error | timeout | dlq — только для показа
|
||||||
export interface JobBrief {
|
export interface JobBrief {
|
||||||
id: string;
|
id: string;
|
||||||
@ -35,7 +54,7 @@ export interface JobBrief {
|
|||||||
error_code: string | null;
|
error_code: string | null;
|
||||||
error_message: string | null;
|
error_message: string | null;
|
||||||
mapping_error: string | null;
|
mapping_error: string | null;
|
||||||
warnings: string[];
|
warnings: FileWarningOut[];
|
||||||
mapping_warnings: string[];
|
mapping_warnings: string[];
|
||||||
finished_at: string | null;
|
finished_at: string | null;
|
||||||
}
|
}
|
||||||
@ -52,6 +71,7 @@ export interface DocumentBrief {
|
|||||||
counterparty_check: CheckResult | null;
|
counterparty_check: CheckResult | null;
|
||||||
locator: string | null;
|
locator: string | null;
|
||||||
comments_count: number;
|
comments_count: number;
|
||||||
|
issue_counts: IssueCountsOut;
|
||||||
}
|
}
|
||||||
export type FileType = 'XLS' | 'XLSX' | 'XLSB' | 'PDF' | 'PNG' | 'JPG';
|
export type FileType = 'XLS' | 'XLSX' | 'XLSB' | 'PDF' | 'PNG' | 'JPG';
|
||||||
export interface FileOut {
|
export interface FileOut {
|
||||||
@ -74,6 +94,36 @@ export interface DocumentOut extends DocumentBrief {
|
|||||||
confirmed_by: string | null;
|
confirmed_by: string | null;
|
||||||
confirmed_at: string | null;
|
confirmed_at: string | null;
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
|
issues: IssueOut[];
|
||||||
|
}
|
||||||
|
/** Замечание к документу или форме: предупреждение разбора, нарушение соотношения или мягкая проверка. */
|
||||||
|
export interface IssueOut {
|
||||||
|
id: string;
|
||||||
|
kind: IssueKind;
|
||||||
|
code: string;
|
||||||
|
severity: Severity;
|
||||||
|
message: string;
|
||||||
|
line_ids: string[];
|
||||||
|
column_id: string | null;
|
||||||
|
column_index: number | null;
|
||||||
|
field: string | null;
|
||||||
|
source: 'service' | 'core';
|
||||||
|
relation: string | null;
|
||||||
|
codes: string[];
|
||||||
|
expected: string | null;
|
||||||
|
actual: string | null;
|
||||||
|
}
|
||||||
|
export interface HeaderSourceOut {
|
||||||
|
text: string | null;
|
||||||
|
confidence: string | null;
|
||||||
|
}
|
||||||
|
export interface EditionLineOut {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
export interface IssueCountsOut {
|
||||||
|
error: number;
|
||||||
|
info: number;
|
||||||
}
|
}
|
||||||
/** Уровень строки ОСВ (DBML `osv_group_type`). */
|
/** Уровень строки ОСВ (DBML `osv_group_type`). */
|
||||||
export type OsvGroupType = 'ACCOUNT' | 'DEPARTMENT' | 'COUNTERPARTY' | 'CONTRACT' | 'OTHER';
|
export type OsvGroupType = 'ACCOUNT' | 'DEPARTMENT' | 'COUNTERPARTY' | 'CONTRACT' | 'OTHER';
|
||||||
@ -109,17 +159,25 @@ export interface StatementHeaderOut {
|
|||||||
report_date: HeaderFieldOut;
|
report_date: HeaderFieldOut;
|
||||||
units: HeaderFieldOut;
|
units: HeaderFieldOut;
|
||||||
organization_name: string | null;
|
organization_name: string | null;
|
||||||
|
organization_name_modified: boolean;
|
||||||
okud: string | null;
|
okud: string | null;
|
||||||
okpo: string | null;
|
okpo: string | null;
|
||||||
okopf: string | null;
|
okopf: string | null;
|
||||||
okfs: string | null;
|
okfs: string | null;
|
||||||
|
okei: string | null;
|
||||||
address: string | null;
|
address: string | null;
|
||||||
|
/** поля шапки, которые требуют проверки аналитиком */
|
||||||
|
needs_review_fields: string[];
|
||||||
|
/** что распознано до нормализации, по имени поля */
|
||||||
|
source: Partial<Record<string, HeaderSourceOut>>;
|
||||||
}
|
}
|
||||||
export interface ColumnOut {
|
export interface ColumnOut {
|
||||||
id: string;
|
id: string;
|
||||||
column_index: number;
|
column_index: number;
|
||||||
label: string | null;
|
label: string | null;
|
||||||
|
label_confidence: string | null;
|
||||||
period_date: string | null;
|
period_date: string | null;
|
||||||
|
period_kind: PeriodKind;
|
||||||
is_current: boolean | null;
|
is_current: boolean | null;
|
||||||
}
|
}
|
||||||
export interface ValueOut {
|
export interface ValueOut {
|
||||||
@ -128,18 +186,32 @@ export interface ValueOut {
|
|||||||
column_index: number;
|
column_index: number;
|
||||||
amount: string | null;
|
amount: string | null;
|
||||||
string_value: string | null;
|
string_value: string | null;
|
||||||
|
text: string | null;
|
||||||
confidence: string | null;
|
confidence: string | null;
|
||||||
is_recognized: boolean | null;
|
is_recognized: boolean | null;
|
||||||
is_modified: boolean;
|
is_modified: boolean;
|
||||||
original_amount: string | null;
|
original_amount: string | null;
|
||||||
|
needs_review: boolean;
|
||||||
|
is_confirmed: boolean;
|
||||||
}
|
}
|
||||||
export interface LineOut {
|
export interface LineOut {
|
||||||
id: string | null; // null — строка достроена по справочнику редакции
|
id: string | null; // null — строка достроена по справочнику редакции
|
||||||
origin: 'source' | 'template';
|
origin: 'source' | 'template';
|
||||||
|
row_index: number | null;
|
||||||
|
source_ref: string | null;
|
||||||
|
line_status: LineStatus;
|
||||||
line_code: string | null;
|
line_code: string | null;
|
||||||
|
line_code_text: string | null;
|
||||||
|
line_code_confidence: string | null;
|
||||||
|
line_code_modified: boolean;
|
||||||
line_name: string | null;
|
line_name: string | null;
|
||||||
|
/** справочное имя строки по коду редакции */
|
||||||
|
reference_name: string | null;
|
||||||
// код четырёхзначной строки-родителя у пятизначной подстроки (считает ядро, AURORA-1527); иначе null
|
// код четырёхзначной строки-родителя у пятизначной подстроки (считает ядро, AURORA-1527); иначе null
|
||||||
parent_code: string | null;
|
parent_code: string | null;
|
||||||
|
parent_row_index: number | null;
|
||||||
|
needs_review: boolean;
|
||||||
|
is_confirmed: boolean;
|
||||||
values: ValueOut[];
|
values: ValueOut[];
|
||||||
}
|
}
|
||||||
export interface FailedCheckOut {
|
export interface FailedCheckOut {
|
||||||
@ -147,6 +219,8 @@ export interface FailedCheckOut {
|
|||||||
relation: string;
|
relation: string;
|
||||||
expected: string;
|
expected: string;
|
||||||
actual: string;
|
actual: string;
|
||||||
|
codes: string[];
|
||||||
|
line_ids: string[];
|
||||||
}
|
}
|
||||||
export interface ChecksOut {
|
export interface ChecksOut {
|
||||||
failed: FailedCheckOut[];
|
failed: FailedCheckOut[];
|
||||||
@ -165,17 +239,34 @@ export interface StatementOut {
|
|||||||
counterparty_check: CheckResult | null;
|
counterparty_check: CheckResult | null;
|
||||||
locator: string | null;
|
locator: string | null;
|
||||||
file_id: string;
|
file_id: string;
|
||||||
|
form_variant: 'full' | 'simplified' | null;
|
||||||
};
|
};
|
||||||
edition: string | null;
|
edition: string | null;
|
||||||
header: StatementHeaderOut;
|
header: StatementHeaderOut;
|
||||||
columns: ColumnOut[];
|
columns: ColumnOut[];
|
||||||
lines: LineOut[];
|
lines: LineOut[];
|
||||||
checks: ChecksOut;
|
checks: ChecksOut;
|
||||||
|
issues: IssueOut[];
|
||||||
|
edition_lines: EditionLineOut[];
|
||||||
|
}
|
||||||
|
/** Адрес строки в PATCH: из БД — `line_id`, достроенная — `line_code`. */
|
||||||
|
export type CellTarget = { line_id: string } | { line_code: string };
|
||||||
|
export type CellPatch =
|
||||||
|
| (CellTarget & { column_id: string; amount: string | null })
|
||||||
|
| { line_id: string; column_id: string; confirmed: boolean };
|
||||||
|
export type LinePatch =
|
||||||
|
| { line_id: string; line_code: string }
|
||||||
|
| { line_id: string; line_status: 'detail' }
|
||||||
|
| { line_id: string; confirmed: boolean };
|
||||||
|
export interface ColumnPatch {
|
||||||
|
column_id: string;
|
||||||
|
period_date: string;
|
||||||
}
|
}
|
||||||
export type CellPatch = ({ line_id: string } | { line_code: string }) & { column_id: string; amount: string | null };
|
|
||||||
export interface StatementPatch {
|
export interface StatementPatch {
|
||||||
header?: { inn?: string; report_date?: string; units?: string };
|
header?: { inn?: string; report_date?: string; units?: string; organization_name?: string };
|
||||||
values?: CellPatch[];
|
values?: CellPatch[];
|
||||||
|
columns?: ColumnPatch[];
|
||||||
|
lines?: LinePatch[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CounterpartyType = 'GSK' | 'GK' | 'EXTERNAL';
|
export type CounterpartyType = 'GSK' | 'GK' | 'EXTERNAL';
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import {
|
|||||||
documentStatusLabel,
|
documentStatusLabel,
|
||||||
documentTypeLabel,
|
documentTypeLabel,
|
||||||
fileStatusLabel,
|
fileStatusLabel,
|
||||||
|
lineStatusLabel,
|
||||||
matchKindLabel,
|
matchKindLabel,
|
||||||
periodLabel,
|
periodLabel,
|
||||||
quarterLabel,
|
quarterLabel,
|
||||||
|
severityLabel,
|
||||||
} from './labels';
|
} from './labels';
|
||||||
import { osvPeriodLabel, quarterShortLabel } from './labels';
|
import { osvPeriodLabel, quarterShortLabel } from './labels';
|
||||||
|
|
||||||
@ -71,3 +73,16 @@ describe('подписи периода', () => {
|
|||||||
expect(osvPeriodLabel('странное')).toBe('странное');
|
expect(osvPeriodLabel('странное')).toBe('странное');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('подписи формы v5.19', () => {
|
||||||
|
it('статус строки', () => {
|
||||||
|
expect(lineStatusLabel('ok')).toBe('Строка формы');
|
||||||
|
expect(lineStatusLabel('unreadable')).toBe('Код не распознан');
|
||||||
|
expect(lineStatusLabel('missing')).toBe('Кода нет');
|
||||||
|
expect(lineStatusLabel('detail')).toBe('Расшифровка');
|
||||||
|
});
|
||||||
|
it('уровень замечания', () => {
|
||||||
|
expect(severityLabel('error')).toBe('Ошибка');
|
||||||
|
expect(severityLabel('info')).toBe('Информация');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { CounterpartyType, DocumentStatus, DocumentType, FileStatus, MatchKind, Quarter } from '../types/api';
|
import type { CounterpartyType, DocumentStatus, DocumentType, FileStatus, LineStatus, MatchKind, Quarter, Severity } from '../types/api';
|
||||||
|
|
||||||
const TYPE_LABELS: Record<DocumentType, string> = {
|
const TYPE_LABELS: Record<DocumentType, string> = {
|
||||||
BALANCE: 'Бух. баланс',
|
BALANCE: 'Бух. баланс',
|
||||||
@ -70,3 +70,14 @@ const MATCH_KIND: Record<MatchKind, string> = {
|
|||||||
|
|
||||||
export const counterpartyTypeLabel = (t: CounterpartyType): string => COUNTERPARTY_TYPE[t] ?? t;
|
export const counterpartyTypeLabel = (t: CounterpartyType): string => COUNTERPARTY_TYPE[t] ?? t;
|
||||||
export const matchKindLabel = (k: MatchKind | null): string => (k ? (MATCH_KIND[k] ?? k) : 'не сопоставлен');
|
export const matchKindLabel = (k: MatchKind | null): string => (k ? (MATCH_KIND[k] ?? k) : 'не сопоставлен');
|
||||||
|
|
||||||
|
const LINE_STATUS: Record<LineStatus, string> = {
|
||||||
|
ok: 'Строка формы',
|
||||||
|
unreadable: 'Код не распознан',
|
||||||
|
missing: 'Кода нет',
|
||||||
|
detail: 'Расшифровка',
|
||||||
|
};
|
||||||
|
export const lineStatusLabel = (s: LineStatus): string => LINE_STATUS[s] ?? s;
|
||||||
|
|
||||||
|
const SEVERITY: Record<Severity, string> = { error: 'Ошибка', info: 'Информация' };
|
||||||
|
export const severityLabel = (s: Severity): string => SEVERITY[s] ?? s;
|
||||||
|
|||||||
1006
src/widgets/DocumentReview/__fixtures__/statement-v2.json
Normal file
1006
src/widgets/DocumentReview/__fixtures__/statement-v2.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,15 @@ const row = (code: string | null, name: string | null = `строка ${code}`):
|
|||||||
name,
|
name,
|
||||||
parentCode: null,
|
parentCode: null,
|
||||||
cells: {},
|
cells: {},
|
||||||
|
referenceName: null,
|
||||||
|
lineStatus: 'ok',
|
||||||
|
rowIndex: null,
|
||||||
|
parentRowIndex: null,
|
||||||
|
codeText: code,
|
||||||
|
codeConfidence: null,
|
||||||
|
needsReview: false,
|
||||||
|
isConfirmed: false,
|
||||||
|
codeModified: false,
|
||||||
});
|
});
|
||||||
const sub = (code: string, parentCode: string | null): GridRow => ({ ...row(code), parentCode });
|
const sub = (code: string, parentCode: string | null): GridRow => ({ ...row(code), parentCode });
|
||||||
const shape = (rows: ReturnType<typeof formLayout>) =>
|
const shape = (rows: ReturnType<typeof formLayout>) =>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import type { StatementOut, ValueOut } from '../../types/api';
|
import type { StatementOut, ValueOut } from '../../types/api';
|
||||||
|
import statementV2Json from './__fixtures__/statement-v2.json';
|
||||||
import type { GridRow } from './statementGrid';
|
import type { GridRow } from './statementGrid';
|
||||||
import { applyDraftInput, buildStatementGrid, cellFlags, cellKey, draftToPatch, emptySublineKeys } from './statementGrid';
|
import { applyDraftInput, buildStatementGrid, cellFlags, cellKey, draftToPatch, emptySublineKeys } from './statementGrid';
|
||||||
|
|
||||||
@ -9,13 +10,19 @@ const v = (id: string | null, column_id: string, column_index: number, extra: Pa
|
|||||||
column_index,
|
column_index,
|
||||||
amount: '1.00',
|
amount: '1.00',
|
||||||
string_value: null,
|
string_value: null,
|
||||||
|
text: null,
|
||||||
confidence: '100',
|
confidence: '100',
|
||||||
is_recognized: true,
|
is_recognized: true,
|
||||||
is_modified: false,
|
is_modified: false,
|
||||||
original_amount: null,
|
original_amount: null,
|
||||||
|
needs_review: false,
|
||||||
|
is_confirmed: false,
|
||||||
...extra,
|
...extra,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Фикстура v5.19 (тип StatementOut): приведение нужно, т.к. импорт JSON расширяет строковые литералы до string
|
||||||
|
const statementV2 = statementV2Json as StatementOut;
|
||||||
|
|
||||||
const statement = {
|
const statement = {
|
||||||
columns: [
|
columns: [
|
||||||
{ id: 'c2', column_index: 1, label: '31.12.2023', period_date: null, is_current: false },
|
{ id: 'c2', column_index: 1, label: '31.12.2023', period_date: null, is_current: false },
|
||||||
@ -32,6 +39,32 @@ describe('buildStatementGrid', () => {
|
|||||||
expect(grid.rows[0].cells.c1?.id).toBe('v1');
|
expect(grid.rows[0].cells.c1?.id).toBe('v1');
|
||||||
expect(grid.rows[0].cells.c2?.id).toBe('v2');
|
expect(grid.rows[0].cells.c2?.id).toBe('v2');
|
||||||
});
|
});
|
||||||
|
it('буквально переносит поля v2 из фикстуры', () => {
|
||||||
|
const { rows } = buildStatementGrid(statementV2);
|
||||||
|
const byKey = new Map(rows.map((row) => [row.key, row]));
|
||||||
|
expect(byKey.get('l-u7')).toMatchObject({
|
||||||
|
lineStatus: 'unreadable',
|
||||||
|
rowIndex: 7,
|
||||||
|
codeText: 'ИЧ,250',
|
||||||
|
code: null,
|
||||||
|
key: 'l-u7',
|
||||||
|
});
|
||||||
|
expect(byKey.get('l-d2')?.parentRowIndex).toBe(1);
|
||||||
|
expect(byKey.get('code-1250')).toMatchObject({
|
||||||
|
key: 'code-1250',
|
||||||
|
rowIndex: null,
|
||||||
|
referenceName: 'Денежные средства и денежные эквиваленты',
|
||||||
|
});
|
||||||
|
expect(byKey.get('l-1230')).toMatchObject({ needsReview: true, codeConfidence: '50' });
|
||||||
|
});
|
||||||
|
it('каждая пара «строка × колонка» фикстуры имеет ValueOut', () => {
|
||||||
|
const { columns, rows } = buildStatementGrid(statementV2);
|
||||||
|
for (const row of rows) {
|
||||||
|
for (const column of columns) {
|
||||||
|
expect(row.cells[column.id], `${row.key} × ${column.id}`).toBeDefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
it('достроенная строка: ключ по коду, lineId = null', () => {
|
it('достроенная строка: ключ по коду, lineId = null', () => {
|
||||||
const withTemplate = {
|
const withTemplate = {
|
||||||
...statement,
|
...statement,
|
||||||
@ -51,22 +84,28 @@ describe('buildStatementGrid', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cellFlags', () => {
|
describe('cellFlags — внимание только по needs_review', () => {
|
||||||
it('внимание: уверенность < 100 или не распознано', () => {
|
it('низкая уверенность и is_recognized=false без needs_review — не внимание', () => {
|
||||||
expect(cellFlags(v('a', 'c', 0, { confidence: '87.5' })).attention).toBe(true);
|
expect(cellFlags(v('a', 'c', 0, { confidence: '50', is_recognized: false, needs_review: false })).attention).toBe(false);
|
||||||
expect(cellFlags(v('a', 'c', 0, { is_recognized: false })).attention).toBe(true);
|
});
|
||||||
expect(cellFlags(v('a', 'c', 0)).attention).toBe(false);
|
it('needs_review=true — внимание, даже при уверенности 100', () => {
|
||||||
expect(cellFlags(v('a', 'c', 0, { confidence: null })).attention).toBe(false);
|
expect(cellFlags(v('a', 'c', 0, { confidence: '100', needs_review: true })).attention).toBe(true);
|
||||||
|
});
|
||||||
|
it('is_confirmed — подтверждена, уверенность не меняется', () => {
|
||||||
|
expect(cellFlags(v('a', 'c', 0, { is_confirmed: true })).confirmed).toBe(true);
|
||||||
|
});
|
||||||
|
it('undefined — все флаги false', () => {
|
||||||
|
expect(cellFlags(undefined)).toEqual({ attention: false, modified: false, confirmed: false });
|
||||||
});
|
});
|
||||||
it('пустая достроенная ячейка не требует внимания', () => {
|
it('пустая достроенная ячейка не требует внимания', () => {
|
||||||
expect(cellFlags(v(null, 'c', 0, { amount: null, confidence: null, is_recognized: null }))).toEqual({
|
expect(cellFlags(v(null, 'c', 0, { amount: null, confidence: null, is_recognized: null }))).toEqual({
|
||||||
attention: false,
|
attention: false,
|
||||||
modified: false,
|
modified: false,
|
||||||
|
confirmed: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('исправлено', () => {
|
it('исправлено', () => {
|
||||||
expect(cellFlags(v('a', 'c', 0, { is_modified: true })).modified).toBe(true);
|
expect(cellFlags(v('a', 'c', 0, { is_modified: true })).modified).toBe(true);
|
||||||
expect(cellFlags(undefined)).toEqual({ attention: false, modified: false });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -129,6 +168,15 @@ describe('parent_code и пустые строки детализации', () =
|
|||||||
origin: 'source',
|
origin: 'source',
|
||||||
parentCode,
|
parentCode,
|
||||||
cells,
|
cells,
|
||||||
|
referenceName: null,
|
||||||
|
lineStatus: 'ok',
|
||||||
|
rowIndex: null,
|
||||||
|
parentRowIndex: null,
|
||||||
|
codeText: key,
|
||||||
|
codeConfidence: null,
|
||||||
|
needsReview: false,
|
||||||
|
isConfirmed: false,
|
||||||
|
codeModified: false,
|
||||||
});
|
});
|
||||||
it('пустая подстрока: все ячейки без суммы и без строки; родитель и нулевая сумма — не пустые', () => {
|
it('пустая подстрока: все ячейки без суммы и без строки; родитель и нулевая сумма — не пустые', () => {
|
||||||
const rows = [
|
const rows = [
|
||||||
|
|||||||
@ -1,13 +1,26 @@
|
|||||||
import type { CellPatch, ColumnOut, StatementOut, ValueOut } from '../../types/api';
|
import type { CellPatch, ColumnOut, LineStatus, StatementOut, ValueOut } from '../../types/api';
|
||||||
|
|
||||||
export type GridRow = {
|
export type GridRow = {
|
||||||
key: string;
|
key: string;
|
||||||
lineId: string | null;
|
lineId: string | null;
|
||||||
code: string | null;
|
code: string | null;
|
||||||
|
/** line_name — наименование из файла */
|
||||||
name: string | null;
|
name: string | null;
|
||||||
|
/** справочное имя строки по коду редакции */
|
||||||
|
referenceName: string | null;
|
||||||
origin: 'source' | 'template';
|
origin: 'source' | 'template';
|
||||||
/** Родитель строки детализации — отдаёт ядро (`parent_code`); своего правила на фронте нет. */
|
/** Родитель строки детализации — отдаёт ядро (`parent_code`); своего правила на фронте нет. */
|
||||||
parentCode: string | null;
|
parentCode: string | null;
|
||||||
|
lineStatus: LineStatus;
|
||||||
|
/** порядковый номер строки в файле; null у достроенной и созданной правкой */
|
||||||
|
rowIndex: number | null;
|
||||||
|
parentRowIndex: number | null;
|
||||||
|
/** код как распознан в файле (например «ИЧ,250»), даже если `code` не распознан */
|
||||||
|
codeText: string | null;
|
||||||
|
codeConfidence: string | null;
|
||||||
|
needsReview: boolean;
|
||||||
|
isConfirmed: boolean;
|
||||||
|
codeModified: boolean;
|
||||||
cells: Record<string, ValueOut | undefined>;
|
cells: Record<string, ValueOut | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,21 +39,32 @@ export function buildStatementGrid(statement: StatementOut): { columns: ColumnOu
|
|||||||
lineId: line.id,
|
lineId: line.id,
|
||||||
code: line.line_code,
|
code: line.line_code,
|
||||||
name: line.line_name,
|
name: line.line_name,
|
||||||
|
referenceName: line.reference_name,
|
||||||
origin: line.origin,
|
origin: line.origin,
|
||||||
parentCode: line.parent_code ?? null,
|
parentCode: line.parent_code ?? null,
|
||||||
|
lineStatus: line.line_status,
|
||||||
|
rowIndex: line.row_index,
|
||||||
|
parentRowIndex: line.parent_row_index,
|
||||||
|
codeText: line.line_code_text,
|
||||||
|
codeConfidence: line.line_code_confidence,
|
||||||
|
needsReview: line.needs_review,
|
||||||
|
isConfirmed: line.is_confirmed,
|
||||||
|
codeModified: line.line_code_modified,
|
||||||
cells,
|
cells,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return { columns, rows };
|
return { columns, rows };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Уверенность — строка Decimal «0..100»; сравнение с 100 — это не сумма, float здесь безопасен. */
|
/**
|
||||||
export function cellFlags(value: ValueOut | undefined): { attention: boolean; modified: boolean } {
|
* Флаги ячейки: внимание — только по `needs_review` (уверенность решает ядро, сама по себе не подсвечивает);
|
||||||
|
* подтверждённая ячейка не меняет уверенность, это отдельный флаг.
|
||||||
|
*/
|
||||||
|
export function cellFlags(value: ValueOut | undefined): { attention: boolean; modified: boolean; confirmed: boolean } {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return { attention: false, modified: false };
|
return { attention: false, modified: false, confirmed: false };
|
||||||
}
|
}
|
||||||
const lowConfidence = value.confidence !== null && Number(value.confidence) < 100;
|
return { attention: value.needs_review, modified: value.is_modified, confirmed: value.is_confirmed };
|
||||||
return { attention: lowConfidence || value.is_recognized === false, modified: value.is_modified };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cellKey = (rowKeyValue: string, columnId: string) => `${rowKeyValue}:${columnId}`;
|
export const cellKey = (rowKeyValue: string, columnId: string) => `${rowKeyValue}:${columnId}`;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const doc = (over: Partial<DocumentBrief> = {}): DocumentBrief => ({
|
|||||||
counterparty_check: null,
|
counterparty_check: null,
|
||||||
locator: null,
|
locator: null,
|
||||||
comments_count: 0,
|
comments_count: 0,
|
||||||
|
issue_counts: { error: 0, info: 0 },
|
||||||
...over,
|
...over,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user