Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
161 lines
8.6 KiB
TypeScript
161 lines
8.6 KiB
TypeScript
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);
|
||
});
|
||
});
|