arm-web/src/widgets/DocumentReview/osvRows.test.ts
Andrey Epifancev 94cb0a4d74 fix(DocumentReview): заголовок колонки аналитики по виду, ИНН только при контрагентах (#39, AURORA-1666)
Заголовок колонки считается по видам строк-листьев (osvRows.analyticsHeading): «Контрагент»,
название вида из файла или «Аналитика» с подсказкой. Колонка ИНН и подсказка поиска — только при
контрагентах; labelSpan и ширина scroll.x зависят от числа колонок. Требование — в specs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:04:17 +04:00

168 lines
7.8 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 { EntryOut, OsvGroupType } from '../../types/api';
import { flattenEntries } from './osvRows';
let row = 0;
const e = (
id: string,
parent: string | null,
groupType: OsvGroupType,
value: string,
amounts: Partial<Pick<EntryOut, 'opening_debit' | 'turnover_debit' | 'closing_credit'>> = {},
extra: Partial<EntryOut> = {},
): EntryOut => ({
id,
row_number: ++row,
level: null,
parent_entry_id: parent,
row_value: value,
group_type: groupType,
group_name: null,
account_code: null,
counterparty_name: null,
counterparty_inn: null,
opening_debit: null,
opening_credit: null,
turnover_debit: null,
turnover_credit: null,
closing_debit: null,
closing_credit: null,
...amounts,
...extra,
});
describe('flattenEntries', () => {
it('строки — только аналитика нижнего уровня с субсчётом', () => {
const { rows } = flattenEntries([
e('62', null, 'ACCOUNT', '62', { opening_debit: '10' }, { account_code: '62' }),
e('62.01', '62', 'ACCOUNT', '62.01', {}, { account_code: '62.01' }),
e('a', '62.01', 'COUNTERPARTY', 'ООО А', { opening_debit: '7' }, { account_code: '62.01', counterparty_inn: '123' }),
e('b', '62.01', 'COUNTERPARTY', 'ООО Б', { opening_debit: '3' }, { account_code: '62.01' }),
]);
expect(rows.map((r) => [r.account, r.analytics, r.inn, r.opening_debit])).toEqual([
['62.01', 'ООО А', '123', '7'],
['62.01', 'ООО Б', null, '3'],
]);
});
it('подразделение — из родительской строки DEPARTMENT', () => {
const { rows, hasDepartments } = flattenEntries([
e('62', null, 'ACCOUNT', '62', {}, { account_code: '62' }),
e('62.01', '62', 'ACCOUNT', '62.01', {}, { account_code: '62.01' }),
e('d', '62.01', 'DEPARTMENT', 'БП Аренда', {}, { account_code: '62.01' }),
e('a', 'd', 'COUNTERPARTY', 'РБ', {}, { account_code: '62.01' }),
]);
expect(hasDepartments).toBe(true);
expect(rows.map((r) => [r.department, r.analytics])).toEqual([['БП Аренда', 'РБ']]);
});
it('договоры не отдельными строками: остаётся строка контрагента', () => {
const { rows } = flattenEntries([
e('58', null, 'ACCOUNT', '58.03', {}, { account_code: '58.03' }),
e('a', '58', 'COUNTERPARTY', 'РБ ТК', { opening_debit: '100' }, { account_code: '58.03' }),
e('c1', 'a', 'CONTRACT', 'Договор 1', { opening_debit: '60' }, { account_code: '58.03' }),
e('c2', 'a', 'CONTRACT', 'Договор 2', { opening_debit: '40' }, { account_code: '58.03' }),
]);
expect(rows.map((r) => [r.analytics, r.opening_debit])).toEqual([['РБ ТК', '100']]);
});
it('объекты ОС (OTHER) — строки аналитики', () => {
const { rows } = flattenEntries([
e('01', null, 'ACCOUNT', '01', {}, { account_code: '01' }),
e('o', '01', 'OTHER', 'Автопогрузчик', { opening_debit: '5' }, { account_code: '01.01' }),
]);
expect(rows.map((r) => [r.account, r.analytics])).toEqual([['01.01', 'Автопогрузчик']]);
});
it('субсчёт и подразделение без нижних строк — строка с пустой аналитикой; пустые без сумм — нет', () => {
const { rows } = flattenEntries([
e('62', null, 'ACCOUNT', '62', {}, { account_code: '62' }),
e('62.21', '62', 'ACCOUNT', '62.21', { closing_credit: '128041.39' }, { account_code: '62.21' }),
e('62.22', '62', 'ACCOUNT', '62.22', {}, { account_code: '62.22' }),
e('d', '62', 'DEPARTMENT', 'Опт', { turnover_debit: '1' }, { account_code: '62' }),
]);
expect(rows.map((r) => [r.account, r.department, r.analytics])).toEqual([
['62.21', null, null],
['62', 'Опт', null],
]);
});
it('итог — сумма корневых строк, а не листьев (развёрнутое сальдо 1С)', () => {
const { total } = flattenEntries([
e('60', null, 'ACCOUNT', '60', { closing_credit: '100.10' }, { account_code: '60' }),
e('a', '60', 'COUNTERPARTY', 'А', { closing_credit: '150.10' }, { account_code: '60' }),
e('76', null, 'ACCOUNT', '76', { closing_credit: '0.20' }, { account_code: '76' }),
]);
expect(total.closing_credit).toBe('100.30');
expect(total.opening_debit).toBeNull();
});
it('субсчета для фильтра — уникальные, в натуральном порядке', () => {
const { accounts } = flattenEntries([
e('62', null, 'ACCOUNT', '62', {}, { account_code: '62' }),
e('b', '62', 'COUNTERPARTY', 'Б', {}, { account_code: '62.10' }),
e('a', '62', 'COUNTERPARTY', 'А', {}, { account_code: '62.02' }),
e('c', '62', 'COUNTERPARTY', 'В', {}, { account_code: '62.02' }),
]);
expect(accounts).toEqual(['62.02', '62.10']);
});
it('пустой документ', () => {
expect(flattenEntries([])).toEqual({
rows: [],
total: expect.objectContaining({ opening_debit: null }),
accounts: [],
hasDepartments: false,
analyticsTitle: 'Аналитика',
analyticsHint: null,
hasCounterparties: false,
});
});
});
describe('flattenEntries: вид аналитики', () => {
const acc = e('60', null, 'ACCOUNT', '60', {}, { account_code: '60' });
const cp = (id: string) => e(id, '60', 'COUNTERPARTY', `ООО ${id}`, { opening_debit: '1' }, { group_name: 'Контрагенты' });
const os = (id: string, name: string | null = 'Основные средства') =>
e(id, '60', 'OTHER', `Объект ${id}`, { opening_debit: '1' }, { group_name: name });
it('только контрагенты — «Контрагент», ИНН и поиск по ИНН нужны', () => {
const t = flattenEntries([acc, cp('a'), cp('b')]);
expect([t.analyticsTitle, t.analyticsHint, t.hasCounterparties]).toEqual(['Контрагент', null, true]);
});
it('договор под контрагентом вид не меняет', () => {
const t = flattenEntries([acc, cp('a'), e('d', 'a', 'CONTRACT', 'Договор 1', { opening_debit: '1' }, { group_name: 'Договоры' })]);
expect([t.analyticsTitle, t.hasCounterparties]).toEqual(['Контрагент', true]);
});
it('только основные средства — название вида из файла, без ИНН', () => {
const t = flattenEntries([acc, os('a'), os('b')]);
expect([t.analyticsTitle, t.analyticsHint, t.hasCounterparties]).toEqual(['Основные средства', null, false]);
});
it('смесь видов — «Аналитика» и перечень видов в подсказке', () => {
const t = flattenEntries([acc, cp('a'), os('b')]);
expect([t.analyticsTitle, t.analyticsHint, t.hasCounterparties]).toEqual([
'Аналитика',
'Виды аналитики: Контрагенты, Основные средства',
true,
]);
});
it('два разных вида не-контрагентов — тоже «Аналитика»', () => {
const t = flattenEntries([acc, os('a'), os('b', 'Номенклатура')]);
expect([t.analyticsTitle, t.analyticsHint]).toEqual(['Аналитика', 'Виды аналитики: Основные средства, Номенклатура']);
});
it('вид без названия — «Аналитика»', () => {
const t = flattenEntries([acc, os('a', null)]);
expect([t.analyticsTitle, t.hasCounterparties]).toEqual(['Аналитика', false]);
});
it('структурные строки вид не определяют; без листьев аналитики — «Аналитика» без ИНН', () => {
const t = flattenEntries([e('60', null, 'ACCOUNT', '60', { opening_debit: '5' }, { account_code: '60' })]);
expect([t.analyticsTitle, t.analyticsHint, t.hasCounterparties]).toEqual(['Аналитика', null, false]);
});
});