Перенесён диапазон arm-web a3c0010..de4e477 (1 коммитов, 4 файлов набора синхронизации) скриптом docs/scripts/sync-web.sh. После переноса web/ и arm-web@test идентичны по набору. Коммиты arm-web: - e202711 fix(DocumentReview): просмотр ОСВ как в 1С — строки счетов и субсчетов, без строк-показателей (#51, AURORA-1702, AURORA-1703) Co-Authored-By: sync-web.sh <noreply@aepif.ru>
287 lines
14 KiB
TypeScript
287 lines
14 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import type { EntryOut, OsvGroupType } from '../../types/api';
|
||
import { filterRows, 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,
|
||
data_type: 'BU',
|
||
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('все строки в порядке файла: счёт и субсчёт — группы, вложенные — с отступом по уровню (AURORA-1702)', () => {
|
||
const { rows } = flattenEntries([
|
||
e('62', null, 'ACCOUNT', '62', { opening_debit: '10' }, { account_code: '62' }),
|
||
e('62.01', '62', 'ACCOUNT', '62.01', { opening_debit: '10' }, { 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, r.depth, r.isGroup])).toEqual([
|
||
['62', '62', null, '10', 0, true],
|
||
['62.01', '62.01', null, '10', 1, true],
|
||
['62.01', 'ООО А', '123', '7', 2, false],
|
||
['62.01', 'ООО Б', null, '3', 2, false],
|
||
]);
|
||
});
|
||
|
||
it('группа без сумм всё равно выводится — это заголовок вложенных строк', () => {
|
||
const { rows } = flattenEntries([
|
||
e('62', null, 'ACCOUNT', '62', {}, { account_code: '62' }),
|
||
e('a', '62', 'COUNTERPARTY', 'ООО А', { opening_debit: '7' }, { account_code: '62' }),
|
||
]);
|
||
expect(rows.map((r) => [r.analytics, r.isGroup])).toEqual([
|
||
['62', true],
|
||
['ООО А', false],
|
||
]);
|
||
});
|
||
|
||
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, r.isGroup])).toEqual([
|
||
[null, '62', true],
|
||
[null, '62.01', true],
|
||
['БП Аренда', null, true],
|
||
['БП Аренда', 'РБ', false],
|
||
]);
|
||
});
|
||
|
||
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, r.isGroup])).toEqual([
|
||
['58.03', null, true],
|
||
['РБ ТК', '100', false],
|
||
]);
|
||
});
|
||
|
||
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.filter((r) => !r.isGroup).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, r.isGroup])).toEqual([
|
||
['62', null, '62', true],
|
||
['62.21', null, '62.21', false],
|
||
['62', 'Опт', null, false],
|
||
]);
|
||
});
|
||
|
||
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', '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: строки-показатели «Кол.», «НУ», «Вал.» (AURORA-1703)', () => {
|
||
const ind = (id: string, parent: string, label: string, dataType: EntryOut['data_type'], amount: string) =>
|
||
e(id, parent, 'OTHER', label, { opening_debit: amount }, { account_code: '41.01', data_type: dataType });
|
||
|
||
it('строка БУ остаётся строкой с рублями, показатели под ней не выводятся', () => {
|
||
const { rows } = flattenEntries([
|
||
e('41', null, 'ACCOUNT', '41.01', { opening_debit: '1000.00' }, { account_code: '41.01' }),
|
||
e('n', '41', 'OTHER', 'Мука', { opening_debit: '1000.00' }, { account_code: '41.01', group_name: 'Номенклатура' }),
|
||
ind('q', 'n', 'Кол.', 'QUANTITY', '25.000'),
|
||
ind('t', 'n', 'НУ', 'NU', '990.00'),
|
||
ind('c', 'n', 'Вал.', 'CURRENCY', '11.00'),
|
||
ind('p', 'n', 'ПР', null, '10.00'),
|
||
]);
|
||
expect(rows.map((r) => [r.analytics, r.opening_debit, r.isGroup])).toEqual([
|
||
['41.01', '1000.00', true],
|
||
['Мука', '1000.00', false],
|
||
]);
|
||
});
|
||
|
||
it('показатель под строкой счёта не делает счёт группой без вложенных строк', () => {
|
||
const { rows } = flattenEntries([
|
||
e('10', null, 'ACCOUNT', '10.01', { opening_debit: '5.00' }, { account_code: '10.01' }),
|
||
ind('q', '10', 'Кол.', 'QUANTITY', '3.000'),
|
||
]);
|
||
expect(rows.map((r) => [r.analytics, r.opening_debit, r.isGroup])).toEqual([['10.01', '5.00', false]]);
|
||
});
|
||
|
||
it('показатели не определяют вид аналитики', () => {
|
||
const t = flattenEntries([
|
||
e('41', null, 'ACCOUNT', '41.01', {}, { account_code: '41.01' }),
|
||
e('n', '41', 'OTHER', 'Мука', { opening_debit: '1' }, { group_name: 'Номенклатура' }),
|
||
ind('q', 'n', 'Кол.', 'QUANTITY', '1'),
|
||
]);
|
||
expect([t.analyticsTitle, t.analyticsHint]).toEqual(['Номенклатура', null]);
|
||
});
|
||
|
||
it('документ целиком в НУ (вид из шапки) — строки обычные, не показатели', () => {
|
||
const nu = { data_type: 'NU' as const };
|
||
const { rows } = flattenEntries([
|
||
e('68', null, 'ACCOUNT', '68', { opening_debit: '5' }, { account_code: '68', ...nu }),
|
||
e('a', '68', 'COUNTERPARTY', 'ИФНС', { opening_debit: '5' }, { account_code: '68', ...nu }),
|
||
]);
|
||
expect(rows.map((r) => r.analytics)).toEqual(['68', 'ИФНС']);
|
||
});
|
||
|
||
it('строка «БУ» под счётом с видом из шапки SUM — обычная строка, не показатель', () => {
|
||
const { rows } = flattenEntries([
|
||
e('41', null, 'ACCOUNT', '41.01', { opening_debit: '5' }, { account_code: '41.01', data_type: 'SUM' }),
|
||
e('n', '41', 'OTHER', 'Мука', { opening_debit: '5' }, { account_code: '41.01', data_type: 'BU' }),
|
||
]);
|
||
expect(rows.map((r) => r.analytics)).toEqual(['41.01', 'Мука']);
|
||
});
|
||
|
||
it('старые документы без вида строк (data_type null) — дерево как раньше', () => {
|
||
const none = { data_type: null };
|
||
const { rows } = flattenEntries([
|
||
e('60', null, 'ACCOUNT', '60', { opening_debit: '5' }, { account_code: '60', ...none }),
|
||
e('o', '60', 'OTHER', 'Прочее', { opening_debit: '5' }, { account_code: '60', ...none }),
|
||
]);
|
||
expect(rows.map((r) => [r.analytics, r.isGroup])).toEqual([
|
||
['60', true],
|
||
['Прочее', 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]);
|
||
});
|
||
});
|
||
|
||
describe('filterRows', () => {
|
||
const { rows } = flattenEntries([
|
||
e('62', null, 'ACCOUNT', '62', { opening_debit: '10' }, { account_code: '62' }),
|
||
e('62.01', '62', 'ACCOUNT', '62.01', { opening_debit: '7' }, { account_code: '62.01' }),
|
||
e('a', '62.01', 'COUNTERPARTY', 'ООО Ромашка', { opening_debit: '7' }, { account_code: '62.01', counterparty_inn: '7701' }),
|
||
e('62.02', '62', 'ACCOUNT', '62.02', { opening_debit: '3' }, { account_code: '62.02' }),
|
||
e('b', '62.02', 'COUNTERPARTY', 'ООО Лютик', { opening_debit: '3' }, { account_code: '62.02' }),
|
||
e('620', null, 'ACCOUNT', '620', { opening_debit: '1' }, { account_code: '620' }),
|
||
]);
|
||
const keys = (list: typeof rows) => list.map((r) => r.key);
|
||
|
||
it('без фильтров — все строки', () => {
|
||
expect(keys(filterRows(rows, '', undefined))).toEqual(['62', '62.01', 'a', '62.02', 'b', '620']);
|
||
});
|
||
|
||
it('субсчёт — его строка и вложенные', () => {
|
||
expect(keys(filterRows(rows, '', '62.01'))).toEqual(['62.01', 'a']);
|
||
});
|
||
|
||
it('счёт — он сам и все субсчета, но не счёт с тем же началом кода', () => {
|
||
expect(keys(filterRows(rows, '', '62'))).toEqual(['62', '62.01', 'a', '62.02', 'b']);
|
||
});
|
||
|
||
it('поиск скрывает строки-группы и ищет по аналитике без учёта регистра и по ИНН', () => {
|
||
expect(keys(filterRows(rows, 'ромаш', undefined))).toEqual(['a']);
|
||
expect(keys(filterRows(rows, ' 7701 ', undefined))).toEqual(['a']);
|
||
expect(keys(filterRows(rows, '62', undefined))).toEqual(['620']);
|
||
});
|
||
});
|