Andrey Epifancev 44e6afc774 fix(table): разрядность не трогает неденежные и текстовые колонки (AURORA-1899)
Возврат тестирования 11.11:
- tableColumns считал множитель по MRT column, у которого нет accessorKey, —
  getMoneyUnit видел пустой ключ и считал денежной любую колонку (НДС, курс,
  сумма в валюте, площадь масштабировались). getMoneyUnit теперь берёт
  column.columnDef ?? column.
- текстовые колонки (комментарии, обоснования, реквизиты договоров, коды)
  попадали в правило «иначе thousand»: «123» в комментарии делилось, а при
  «млн»/«млрд» редактор обоснования закрывался как денежный. Добавлен
  isTextColumn: такие колонки не масштабируются и не форматируются как числа
  (внутренний заказ 10123 и комментарий «123» показываются как есть).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:34:08 +04:00

162 lines
7.2 KiB
JavaScript
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 assert from 'node:assert/strict';
import test from 'node:test';
import {
AHR_LIMIT_SHEET,
DEFAULT_DENOMINATION,
TEXT_COLUMN_IDS,
UNIT_IN_RUB,
getDisplayFactor,
getMoneyUnit,
isTextColumn,
resolveDenomination,
} from './moneyUtils.js';
test('getDisplayFactor converts between units with thousand as the base', () => {
assert.equal(getDisplayFactor('thousand', 'thousand'), 1);
assert.equal(getDisplayFactor('thousand', 'million'), 0.001);
assert.equal(getDisplayFactor('thousand', 'rub'), 1000);
assert.equal(getDisplayFactor('rub', 'thousand'), 0.001);
});
test('getMoneyUnit defaults ordinary money columns to thousand', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.plan.q1' }, 'AHR'), 'thousand');
});
test('getMoneyUnit returns null for quantities, rates, codes and ids regardless of sheet', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.qty_q1' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.contract.vat_rate' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.contract_detail.exchange_rate' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.rented_area' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.header.section_code' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.header.internal_order' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.header.num_group' }), null);
});
test('getMoneyUnit returns null for other *_qty columns via the suffix pattern', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.some_qty' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.header.amount_foreign' }), null);
});
test('getMoneyUnit returns null for TEXT_COLUMN_IDS not covered by the generic suffix list', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.article_id' }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.code_razdela' }), null);
assert.ok(TEXT_COLUMN_IDS.has('data.article_id'));
});
test('getMoneyUnit treats AHR_LIMIT limits and amounts as rub, not thousand', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.amount_q1' }, 'AHR_LIMIT'), 'rub');
assert.equal(getMoneyUnit({ accessorKey: 'data.limit_without_vat' }, 'AHR_LIMIT'), 'rub');
// тот же accessorKey на другом листе — обычная денежная колонка в тыс. руб.
assert.equal(getMoneyUnit({ accessorKey: 'data.amount_q1' }, 'OPER'), 'thousand');
});
test('getMoneyUnit respects an explicit moneyUnit override, including null', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.plan.q1', moneyUnit: null }), null);
assert.equal(getMoneyUnit({ accessorKey: 'data.qty_q1', moneyUnit: 'rub' }), 'rub');
});
// AURORA-1899, возврат тестирования 11.11: tableColumns передаёт в расчёт множителя
// MRT column (column.id + column.columnDef), у которого нет своего accessorKey.
test('getMoneyUnit reads accessorKey from an MRT column object via columnDef', () => {
const mrtColumn = (accessorKey) => ({ id: accessorKey, columnDef: { accessorKey } });
assert.equal(getMoneyUnit(mrtColumn('data.contract_detail.vat_rate'), 'OPER'), null);
assert.equal(getMoneyUnit(mrtColumn('data.contract_detail.exchange_rate'), 'OPER'), null);
assert.equal(getMoneyUnit(mrtColumn('data.contract_detail.amount_foreign'), 'OPER'), null);
assert.equal(getMoneyUnit(mrtColumn('data.header.internal_order'), 'OPER'), null);
assert.equal(getMoneyUnit(mrtColumn('data.rented_area'), 'AHR_RENT'), null);
assert.equal(getMoneyUnit(mrtColumn('data.plan.q1'), 'OPER'), 'thousand');
assert.equal(getMoneyUnit(mrtColumn('data.amount_q1'), 'AHR_LIMIT'), 'rub');
assert.equal(getMoneyUnit({ id: 'data.plan.q1', columnDef: { accessorKey: 'data.plan.q1', moneyUnit: null } }), null);
});
test('getMoneyUnit returns null for text columns: comments, justifications and other free text', () => {
for (const accessorKey of [
'data.plan.comment',
'data.q2.adj_comment',
'data.q2.base_correction_comment',
'data.q1.transfer_next_comment',
'data.q1.transfer_far_comment',
'data.seq_dfip.justification',
'data.header.justification',
'data.collegial.note',
'data.header.name',
'data.contract.counterparty',
'data.contract.subject',
'data.contract.reference',
'data.contract.deadline',
'data.contract.date',
'data.contract.currency',
'data.contract.act',
'data.contract.scheme',
'data.contract_detail.payment_scheme',
'data.contract_detail.rf_schedule',
'data.contract_detail.addenda',
'data.ckk.proc_plan',
'data.ckk.proc_method',
'data.allocation.property',
'data.collegial.protocol',
'data.collegial.approved',
'data.contract_summary.centralized_flag',
'data.contract_end_date',
'data.object_type',
'data.unit',
'data.address',
]) {
assert.equal(getMoneyUnit({ accessorKey }, 'OPER'), null, accessorKey);
}
});
// Возврат 11.11: внутренний заказ 10123 и комментарий «123» ожидаются как исходный текст,
// а не «10 123,0» / «123,0»; ставки и количества остаются числами.
test('isTextColumn marks codes and free text as text, numeric non-money columns as numbers', () => {
const mrtColumn = (accessorKey) => ({ id: accessorKey, columnDef: { accessorKey } });
for (const accessorKey of [
'data.header.internal_order',
'data.header.section_code',
'data.header.item_id',
'data.plan.comment',
'data.q2.adj_comment',
'data.seq_dfip.justification',
'data.contract.counterparty',
'data.article_id',
]) {
assert.equal(isTextColumn({ accessorKey }), true, accessorKey);
assert.equal(isTextColumn(mrtColumn(accessorKey)), true, `mrt ${accessorKey}`);
}
for (const accessorKey of [
'data.contract_detail.vat_rate',
'data.contract_detail.exchange_rate',
'data.contract_detail.amount_foreign',
'data.rented_area',
'data.qty_q1',
'data.acquired_qty',
'data.plan.q1',
]) {
assert.equal(isTextColumn({ accessorKey }), false, accessorKey);
}
});
test('getMoneyUnit keeps money columns whose names resemble text ones as thousand', () => {
assert.equal(getMoneyUnit({ accessorKey: 'data.collegial.amount' }, 'OPER'), 'thousand');
assert.equal(getMoneyUnit({ accessorKey: 'data.q1.transfer_q2_delay_acts' }, 'OPER'), 'thousand');
assert.equal(getMoneyUnit({ accessorKey: 'data.approved.q1' }, 'OPER'), 'thousand');
assert.equal(getMoneyUnit({ accessorKey: 'data.q1.pay_amount' }, 'OPER'), 'thousand');
});
test('resolveDenomination falls back to thousand when nothing is stored', () => {
assert.equal(resolveDenomination(null), DEFAULT_DENOMINATION);
assert.equal(resolveDenomination(undefined), 'thousand');
assert.equal(resolveDenomination(''), 'thousand');
assert.equal(resolveDenomination('rub'), 'rub');
});
test('UNIT_IN_RUB defines the four denominations the selector supports', () => {
assert.deepEqual(Object.keys(UNIT_IN_RUB).sort(), ['billion', 'million', 'rub', 'thousand']);
});
test('AHR_LIMIT_SHEET names the sheet without a denomination selector', () => {
// Используется tableColumns.jsx/SettingPanel.jsx, чтобы отключить масштабирование и редактирование
// денежных колонок на листе, где значения всегда показываются как есть.
assert.equal(AHR_LIMIT_SHEET, 'AHR_LIMIT');
});