arm-web/src/widgets/DocumentReview/statementDraft.test.ts
2026-10-18 14:03:33 +04:00

161 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest';
import type { StatementOut } from '../../types/api';
import statementV2Json from './__fixtures__/statement-v2.json';
import { type StatementDraft, buildStatementPatch, emptyDraft, isDraftEmpty } from './statementDraft';
import { buildStatementGrid, cellKey } from './statementGrid';
const statement = statementV2Json as StatementOut;
const { rows } = buildStatementGrid(statement);
const ctx = { rows, header: statement.header };
const draftOf = (part: Partial<StatementDraft>): StatementDraft => ({ ...emptyDraft(), ...part });
const build = (part: Partial<StatementDraft>) => buildStatementPatch(draftOf(part), ctx);
const bodyOf = (part: Partial<StatementDraft>) => {
const result = build(part);
if (!result.ok) throw new Error(`ожидали тело, получили ошибки: ${JSON.stringify(result.errors)}`);
return result.body;
};
const errorsOf = (part: Partial<StatementDraft>) => {
const result = build(part);
if (result.ok) throw new Error('ожидали ошибки');
return result.errors;
};
describe('суммы как раньше', () => {
it('строка из БД — line_id, шаблонная — line_code', () => {
expect(bodyOf({ amounts: { [cellKey('l-1210', 'c1')]: '120' } })?.values).toEqual([
{ line_id: 'l-1210', column_id: 'c1', amount: '120' },
]);
expect(bodyOf({ amounts: { [cellKey('code-1250', 'c1')]: '5' } })?.values).toEqual([
{ line_code: '1250', column_id: 'c1', amount: '5' },
]);
});
it('пустой текст — очистка суммы (null)', () => {
expect(bodyOf({ amounts: { [cellKey('l-1210', 'c1')]: '' } })?.values).toEqual([{ line_id: 'l-1210', column_id: 'c1', amount: null }]);
});
it('неверный формат — ошибка amount с ключом ячейки', () => {
const key = cellKey('l-1210', 'c1');
expect(errorsOf({ amounts: { [key]: '12,3,4' } })).toEqual([expect.objectContaining({ kind: 'amount', key })]);
});
it('неизвестная строка — ошибка, а не тихий пропуск', () => {
expect(errorsOf({ amounts: { [cellKey('ghost', 'c1')]: '1' } })[0].kind).toBe('amount');
});
});
describe('подтверждение ячейки', () => {
it('confirms → элемент confirmed', () => {
expect(bodyOf({ confirms: { [cellKey('l-u7', 'c1')]: true } })?.values).toEqual([
{ line_id: 'l-u7', column_id: 'c1', confirmed: true },
]);
});
it('сумма той же ячейки отменяет подтверждение (в элементе ровно одно из amount / confirmed)', () => {
const key = cellKey('l-u7', 'c1');
expect(bodyOf({ amounts: { [key]: '7' }, confirms: { [key]: true } })?.values).toEqual([
{ line_id: 'l-u7', column_id: 'c1', amount: '7' },
]);
});
});
describe('подтверждение ячейки без id значения', () => {
it('пропускается молча (P6-6)', () => {
const row = rows.find((r) => r.key === 'l-u7');
if (!row?.cells.c1) throw new Error('нет ячейки в фикстуре');
const noId = { ...row, cells: { ...row.cells, c1: { ...row.cells.c1, id: null } } };
const result = buildStatementPatch(draftOf({ confirms: { [cellKey('l-u7', 'c1')]: true } }), {
rows: rows.map((r) => (r === row ? noId : r)),
header: statement.header,
});
expect(result).toEqual({ ok: true, body: null });
});
});
describe('действия строки', () => {
it('код, расшифровка, подтверждение кода', () => {
expect(bodyOf({ lines: { 'l-u7': { kind: 'code', code: '1250' } } })?.lines).toEqual([{ line_id: 'l-u7', line_code: '1250' }]);
expect(bodyOf({ lines: { 'l-m8': { kind: 'detail' } } })?.lines).toEqual([{ line_id: 'l-m8', line_status: 'detail' }]);
expect(bodyOf({ lines: { 'l-1230': { kind: 'confirm', confirmed: true } } })?.lines).toEqual([{ line_id: 'l-1230', confirmed: true }]);
});
it('пустой код и код, равный текущему, — не действие', () => {
expect(bodyOf({ lines: { 'l-u7': { kind: 'code', code: '' } } })).toBeNull();
expect(bodyOf({ lines: { 'l-1230': { kind: 'code', code: '1230' } } })).toBeNull();
});
it('код — 4 или 5 цифр', () => {
for (const code of ['12a4', '123']) {
expect(errorsOf({ lines: { 'l-u7': { kind: 'code', code } } })).toEqual([
{ kind: 'code', key: 'l-u7', message: 'Код — 4 или 5 цифр' },
]);
}
expect(bodyOf({ lines: { 'l-u7': { kind: 'code', code: '11501' } } })?.lines).toEqual([{ line_id: 'l-u7', line_code: '11501' }]);
});
it('действие строки и сумма той же строки — оба в теле', () => {
const body = bodyOf({ lines: { 'l-m8': { kind: 'detail' } }, amounts: { [cellKey('l-m8', 'c1')]: '8' } });
expect(body?.lines).toEqual([{ line_id: 'l-m8', line_status: 'detail' }]);
expect(body?.values).toEqual([{ line_id: 'l-m8', column_id: 'c1', amount: '8' }]);
});
it('сумма в шаблонную строку 1250 и назначение 1250 другой строке — конфликт', () => {
expect(errorsOf({ amounts: { [cellKey('code-1250', 'c1')]: '5' }, lines: { 'l-u7': { kind: 'code', code: '1250' } } })).toEqual([
{
kind: 'conflict',
key: 'l-u7',
message: 'Код 1250 назначается строке, а в шаблонную строку 1250 введены суммы — сохраните по очереди',
},
]);
});
});
describe('колонки', () => {
it('дата колонки ГГГГ-ММ-ДД', () => {
expect(bodyOf({ columns: { c2: '2024-12-31' } })?.columns).toEqual([{ column_id: 'c2', period_date: '2024-12-31' }]);
});
it('другой формат и несуществующая дата — ошибка date; пусто — не отправляется', () => {
for (const text of ['31.12.2024', '2024-02-30']) {
expect(errorsOf({ columns: { c2: text } })).toEqual([{ kind: 'date', key: 'c2', message: 'Дата в формате ГГГГ-ММ-ДД' }]);
}
expect(bodyOf({ columns: { c2: '' } })).toBeNull();
});
});
describe('шапка', () => {
it('organization_name обрезается', () => {
expect(bodyOf({ header: { organization_name: ' ООО Ромашка-2 ' } })?.header).toEqual({ organization_name: 'ООО Ромашка-2' });
});
it('значение, равное текущему (после обрезки), не отправляется', () => {
expect(bodyOf({ header: { organization_name: ' ООО Ромашка ' } })).toBeNull();
expect(bodyOf({ header: { inn: '7700000000', units: ' тыс. руб. ' } })).toBeNull();
});
it('введено и стёрто — тела нет', () => {
expect(bodyOf({ header: { inn: ' ', organization_name: '' } })).toBeNull();
});
it('дата из needs_review_fields с тем же значением — подтверждение, уходит', () => {
expect(bodyOf({ header: { report_date: '2025-09-30' } })?.header).toEqual({ report_date: '2025-09-30' });
const noReview = { rows, header: { ...statement.header, needs_review_fields: [] } };
const res = buildStatementPatch(draftOf({ header: { report_date: '2025-09-30' } }), noReview);
expect(res).toEqual({ ok: true, body: null });
});
it('длиннее 1000 символов — ошибка header', () => {
expect(errorsOf({ header: { organization_name: 'я'.repeat(1001) } })[0]).toMatchObject({ kind: 'header', key: 'organization_name' });
expect(bodyOf({ header: { organization_name: 'я'.repeat(1000) } })).not.toBeNull();
});
it('подтверждение даты отчёта отправляет текущее значение', () => {
expect(bodyOf({ header: { confirm_report_date: true } })?.header).toEqual({ report_date: '2025-09-30' });
expect(bodyOf({ header: { confirm_report_date: true, report_date: '2025-06-30' } })?.header).toEqual({ report_date: '2025-06-30' });
});
it('пустое поле не отправляется', () => {
expect(bodyOf({ header: { inn: '' } })).toBeNull();
expect(bodyOf({ header: { inn: ' 7700000001 ', units: '', report_date: '2025-09-30' } })?.header).toEqual({
inn: '7700000001',
report_date: '2025-09-30',
});
});
});
describe('пустой черновик', () => {
it('isDraftEmpty и body null', () => {
expect(isDraftEmpty(emptyDraft())).toBe(true);
expect(buildStatementPatch(emptyDraft(), ctx)).toEqual({ ok: true, body: null });
});
it('любая часть делает черновик непустым', () => {
expect(isDraftEmpty(draftOf({ header: { inn: '1' } }))).toBe(false);
expect(isDraftEmpty(draftOf({ lines: { 'l-m8': { kind: 'detail' } } }))).toBe(false);
});
});